diff --git a/Changelog.md b/Changelog.md index 2b8d998ec0..5d9bdf734b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,7 +2,7 @@ __Blockchain HD Frontend__ _Recent changes_ -# (2015-12-29) +# (2016-01-11) @@ -10,10 +10,90 @@ _Recent changes_ ## Bug Fixes +- **Accounts:** revert accidental change in 433de77b4a + ([f149f45c](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/f149f45c962d0c285f775d9917d5bf40eacca783)) +- **Adverts:** set .rootURL to '/' by default + ([ba77aa9f](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/ba77aa9ff930e268b1fba50fc25b27f0d21c3599)) - **Currency:** correct decimal places for bits and mBTC ([e5d3c6c4](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/e5d3c6c4f2cb2b377b54b259be6561d129f6da18)) +- **Deploy:** + - fix filename for .woff2 fonts + ([c785d759](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/c785d759ac1f72f8314bd2acb8cfe93e3cc9c3dd)) + - ROOT_URL now required for dev, defaults to / in production + ([465c694a](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/465c694af5082dc61667659141cffd23184a3314)) + - dmore aggresive cleaning in dist task + ([fa1d4906](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/fa1d4906ba7d9198eff28527b691116848851a29)) + - clean bower and npm cache in dist task + ([f6349813](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/f6349813fa6f433a903362d3fbdf37e5f3245dfb)) + - move CSS and JS to /js so that relative font path works + ([a0e377e0](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/a0e377e0db41614e086eea4ad4d3c294bbfa0496)) +- **Feedback:** + - missing controller file + ([950f3824](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/950f3824946f3f2ce231098345058d51c6e63612)) + - use new endpoint + ([46348250](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/463482500c01c104e59256505568b61f95c1574f)) - **Login:** disable browser validation and autocomplete ([c0ecd11d](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/c0ecd11da6262a42662cc79e18f0f83648424184)) +- **LostGuid:** use .rootURL + ([9d5df846](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/9d5df8467a8b5354f9785a73dfcbae0686f654a1)) +- **Recover:** fix redirect after recovery + ([f22ca557](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/f22ca557376f8f0b6bca94b42831c45abab243b4)) +- **Signup:** improved validation + ([4baac6ea](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/4baac6ea6b2699861ba3dad8b08ee09ec31878c2)) +- **Sponsors:** use .rootURL + ([f94175e0](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/f94175e045ac90f3b5f46e4b553e8f9b6055609d)) +- **VerifyEmail:** guid in token was ignored + ([76fcf8e2](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/76fcf8e230150d99868934804c7a10789eac381c)) +- **bcAsyncInput:** spinner remained after save, blockchain/My-Wallet-V3-Frontend@867850dbdbb276 + ([1443b338](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/1443b338119628f36e0b8640127883a6239e9fea)) + + +## Features + +- remove beta invite system + ([a2b4d07c](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/a2b4d07c7059e4667d96674458e8e336cc3f06ed)) +- **2FA:** process reset 2FA email link + ([118c884b](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/118c884bfdff0ef5de7dc9882f6810c7c2709455)) +- **Deploy:** pass backend URL to grunt dist + ([eae05c42](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/eae05c42b0db8e838619285c63220e840ced0720)) +- **Login:** + - new browser approval - different browser + ([d53490d1](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/d53490d140265861218fc1f208869c1929cb39af)) + - new browser approval - assuming user is in the same browser + ([58fc7d26](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/58fc7d26166367abb846cb33057c8df5cedc3fb5)) +- **Recovery:** use download attribute for nicer file name and to prevent in-browser viewing + ([1d35f29d](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/1d35f29da815dbfcfd73aea6a14db6793a4c2b33)) +- **Release:** use beta version of my-wallet-v3 bower + ([85fbbd2d](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/85fbbd2d908a4fa1a7e29e5e3f20032e02e8b026)) +- **Reset2FA:** form to reset two step verification + ([18ba4615](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/18ba4615c78516c837c7cfede8820f41751e9531)) +- **Routes:** + - show modal after verifying email + ([4d1bdfaa](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/4d1bdfaaf19be2e8486ae578ba76dd849be76aa9)) + - login and verify email routes and endpoint support + ([206d60b5](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/206d60b51c58e07bbc43a41db46816112cfcfebb)) +- **Unsubscribe:** unsubscribe route + ([632db4c6](https://github.com/blockchain/My-Wallet-HD-Frontend/commit/632db4c634ac6b2792cc0f27310740b2c73ed8e4)) + + +## Refactor + +- **Cookies:** replace references to deprecated with +- **Dependencies:** removed unused npm dependencies. Removed unmaintained E2E tests. +- **RecoverGuid:** move functionality out of controller +- **Signup:** cleaned up controller and improved tests +- **WalletTokenEndpoints:** slightly modified result + + +## Test + +- fixed +- **Mocks:** added MyWalletTokenEndpoints mock + + +## Chore + +- **Changelog:** update for beta branch diff --git a/Gruntfile.coffee b/Gruntfile.coffee index b04974dc88..5d068ad1df 100644 --- a/Gruntfile.coffee +++ b/Gruntfile.coffee @@ -1,10 +1,13 @@ +fs = require("fs") +path = require("path") + module.exports = (grunt) -> grunt.initConfig pkg: grunt.file.readJSON("package.json") clean: { build: ["build"] - dist: ["dist"] + dist: ["dist", "bower_components", "node_modules"] test: ["coverage"] sass: [".sass-cache"] } @@ -25,22 +28,11 @@ module.exports = (grunt) -> options: { context : { PRODUCTION: true - BETA: false } }, expand: true src: ['build/index.html'] dest: '' - beta: - options: { - context : { - PRODUCTION: true - BETA: true - } - }, - expand: true - src: ['build/admin.html', 'build/index-beta.html'] - dest: '' concat: options: @@ -84,8 +76,7 @@ module.exports = (grunt) -> application: # All components should first be minimized. Only trusted sources should be imported as minified.. src: [ - 'bower_components/blockchain-wallet/dist/my-wallet.min.js' - "build/bower_components/jquery/dist/jquery.js" # Duplicate; also included in my-wallet a.t.m. Minified version causes problems. + 'build/bower_components/blockchain-wallet/dist/my-wallet.min.js' 'build/bower_components/angular/angular.min.js' 'build/bower_components/angular-sanitize/angular-sanitize.min.js' 'build/bower_components/angular-cookies/angular-cookies.min.js' @@ -94,17 +85,7 @@ module.exports = (grunt) -> 'build/application-dependencies.min.js' ] - dest: "dist/application.min.js" - - beta: - src: [ - "build/bower_components/jquery/dist/jquery.js" - "build/bower_components/angular/angular.min.js" - "build/bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js" - "build/bower_components/bootstrap/dist/js/bootstrap.min.js" - "app/admin.js" - ] - dest: "dist/beta-admin.js" + dest: "dist/js/application.min.js" coffee: coffee_to_js: @@ -139,15 +120,8 @@ module.exports = (grunt) -> "build/css/blockchain.css" # Needs to be loaded first "build/css/**/*.css" ], - dest: "dist/application.css" - }, - beta: { - src: [ - "build/css/blockchain.css" - "build/css/navigation.css" - ], - dest: "dist/beta-admin.css" - }, + dest: "dist/css/application.css" + } }, autoprefixer: { @@ -201,8 +175,7 @@ module.exports = (grunt) -> main: files: [ {src: ["beep.wav"], dest: "dist/"} - {src: ["index.html", "index-beta.html"], dest: "dist/", cwd: "build", expand: true} - {src: ["admin.html"], dest: "dist/", cwd: "build", expand: true} + {src: ["index.html"], dest: "dist/", cwd: "build", expand: true} {src: ["img/*"], dest: "dist/", cwd: "build", expand: true} {src: ["locales/*"], dest: "dist/", cwd: "build", expand: true} {src: ["**/*"], dest: "dist/fonts", cwd: "build/fonts", expand: true} @@ -216,7 +189,6 @@ module.exports = (grunt) -> css: files: [ - {src: ["intlTelInput.css"], dest: "build/css", cwd: "bower_components/intl-tel-input/build/css", expand: true } {src: ["font-awesome.min.css"], dest: "build/css", cwd: "bower_components/fontawesome/css", expand: true } {src: ["*.css"], dest: "build/css", cwd: "assets/css", expand: true } ] @@ -228,15 +200,6 @@ module.exports = (grunt) -> {src: ["*"], dest: "build/fonts", cwd: "assets/fonts/themify", expand: true} ] - beta: - files: [ - {src: ["beta/betaAdminServer.js"], dest: "dist/", cwd: "app", expand: true} - {src: ["beta/package.json"], dest: "dist/", cwd: "app", expand: true} - ] - - beta_index: - src: "build/index.html" - dest: "build/index-beta.html" images: files: [ @@ -280,7 +243,6 @@ module.exports = (grunt) -> options: client: false files: - "build/admin.html": "app/admin.jade" "build/index.html": "app/index.jade" babel: @@ -295,7 +257,7 @@ module.exports = (grunt) -> }] rename: - assets: # Renames all images, fonts, etc and updates application.min.js, application.css and admin.html with their new names. + assets: # Renames all images, fonts, etc and updates application.min.js and application.css with their new names. options: skipIfHashed: true startSymbol: "{{" @@ -304,33 +266,29 @@ module.exports = (grunt) -> format: "{{basename}}-{{hash}}.{{ext}}" callback: (befores, afters) -> + publicdir = fs.realpathSync("dist") + # Start with the longest file names, so e.g. some-font.woff2 is renamed before some-font.woff. tuples = new Array i = 0 while i < befores.length - tuples.push [befores[i],afters[i]] + tuples.push [path.relative(publicdir, befores[i]),path.relative(publicdir, afters[i])] i++ - tuples.sort((a,b) -> - if a[0].length != b[0].length - return a[0].length < b[0].length - return a < b - ) + tuples.sort((a,b) -> b[0].length - a[0].length) - befores = tuples.map((t)->t[0]) - afters = tuples.map((t)->t[1]) + ordered_befores = tuples.map((t)->t[0]) + ordered_afters = tuples.map((t)->t[1]) - publicdir = require("fs").realpathSync("dist") - path = require("path") - for referring_file_path in ["dist/application.min.js", "dist/beta-admin.js", "dist/application.css", "dist/beta-admin.css", "dist/admin.html", "dist/index.html", "dist/index-beta.html"] + for referring_file_path in ["dist/js/application.min.js", "dist/css/application.css", "dist/index.html"] contents = grunt.file.read(referring_file_path) before = undefined after = undefined i = 0 - while i < befores.length - before = path.relative(publicdir, befores[i]) - after = path.relative(publicdir, afters[i]) + while i < ordered_befores.length + before = ordered_befores[i] + after = ordered_afters[i] contents = contents.split("build/" + before).join(after) contents = contents.split(before).join(after) @@ -347,7 +305,7 @@ module.exports = (grunt) -> 'dist/beep.wav' ] - html: # Renames application/beta.min.js/css and updates index/admin.html + html: # Renames application.min.js/css and updates index.html options: skipIfHashed: true startSymbol: "{{" @@ -356,10 +314,9 @@ module.exports = (grunt) -> format: "{{basename}}-{{hash}}.{{ext}}" callback: (befores, afters) -> - publicdir = require("fs").realpathSync("dist") - path = require("path") + publicdir = fs.realpathSync("dist") - for referring_file_path in ["dist/index.html", "dist/index-beta.html", "dist/admin.html"] + for referring_file_path in ["dist/index.html"] contents = grunt.file.read(referring_file_path) before = undefined after = undefined @@ -375,60 +332,14 @@ module.exports = (grunt) -> files: src: [ - 'dist/application.min.js' - 'dist/application.css' - 'dist/beta-admin.js' - 'dist/beta-admin.css' + 'dist/js/application.min.js' + 'dist/css/application.css' ] shell: - deploy_static_to_dev: - command: () -> - 'rsync -rz --delete dist hd-dev@server:' - - deploy_server_to_dev: - command: () -> - 'rsync -rz --delete server.js hd-dev@server:' - - deploy_beta_to_dev: - command: () -> - 'rsync -rz --delete node_modules/my-wallet-v3-beta-module hd-dev@server:node_modules/' - - deploy_static_to_staging: - command: () -> - 'rsync -rz --delete dist hd-staging@server:' - - deploy_server_to_staging: - command: () -> - 'rsync -rz --delete server.js hd-staging@server:' - - deploy_beta_to_staging: - command: () -> - 'rsync -rz --delete node_modules/my-wallet-v3-beta-module hd-staging@server:node_modules/' - - deploy_static_to_alpha: - command: () -> - 'rsync -rz --delete dist hd-alpha@server:' - - deploy_server_to_alpha: - command: () -> - 'rsync -rz --delete server.js hd-alpha@server:' - - deploy_beta_to_alpha: - command: () -> - 'rsync -rz --delete node_modules/my-wallet-v3-beta-module hd-alpha@server:node_modules/' - - deploy_start_dev: - command: () -> - 'ssh hd-dev@server "./start.sh"' - - deploy_start_staging: - command: () -> - 'ssh hd-staging@server "./start.sh"' - - deploy_start_alpha: + clean_bower_and_npm_cache: command: () -> - 'ssh hd-alpha@server "./start.sh"' + 'bower cache clean && npm cache clean' check_dependencies: command: () -> @@ -440,7 +351,7 @@ module.exports = (grunt) -> npm_install_dependencies: command: () -> - 'cd build && npm install' + 'npm install' bower_install_dependencies: command: () -> @@ -462,8 +373,10 @@ module.exports = (grunt) -> app_name : 'Blockchain HD Frontend', # logo : 'https://raw.githubusercontent.com/blockchain/My-Wallet-HD-Frontend/changelog/assets/icons/png/logo.png', intro : 'Recent changes' - grep_commits: '^fix|^feat|^ui|^copy|^docs|^dep|^refactor|^chore|^test|BREAKING' + grep_commits: '^fix|^feat|^ui|^copy|^docs|^dep|^refactor|^chore|^test|^dev|BREAKING' repo_url: 'https://github.com/blockchain/My-Wallet-HD-Frontend' + branch_name: 'beta' + tag: '1.2.17' coveralls: options: @@ -473,6 +386,23 @@ module.exports = (grunt) -> force: true recursive: true + replace: + root_url: + src: ['build/js/services/wallet.service.js'], + overwrite: true, + replacements: [{ + from: 'customRootURL = $rootScope.rootURL' + to: () => + 'customRootURL = $rootScope.rootURL = "https://' + @rootUrl + '/"' + }] + web_socket_url: + src: ['build/js/services/wallet.service.js'], + overwrite: true, + replacements: [{ + from: 'customWebSocketURL = $rootScope.webSocketURL' + to: () => + 'customWebSocketURL = "wss://' + @rootUrl + '/inv"' + }] grunt.loadNpmTasks "grunt-contrib-uglify" grunt.loadNpmTasks('grunt-contrib-concat') @@ -492,6 +422,7 @@ module.exports = (grunt) -> grunt.loadNpmTasks('git-changelog') grunt.loadNpmTasks('grunt-babel') grunt.loadNpmTasks('grunt-karma-coveralls') + grunt.loadNpmTasks('grunt-text-replace') grunt.registerTask "compile", ["coffee"] @@ -513,123 +444,78 @@ module.exports = (grunt) -> "watch" ] - grunt.registerTask "dist_beta", [ - "concat:beta" - "concat_css:beta" - ] - # Default task(s). - grunt.registerTask "dist", [ - "clean" - "build" - "shell:check_dependencies" - "shell:npm_install_dependencies" - "shell:bower_install_dependencies" - "shell:check_pgp_signatures" - "concat:application_dependencies" - "uglify:application_dependencies" - "concat:application" - "concat_css:app" - "jade" - "copy:beta_index" - "preprocess" - "copy:main" - "copy:beta" - "dist_beta" # We don't check beta dependencies against a whitelist - "rename:assets" - "rename:html" - "git_changelog" - ] - - grunt.registerTask "dist_unsafe", [ - "clean" - "build" - "shell:skip_check_dependencies" - "concat:application_dependencies" - "uglify:application_dependencies" - "concat:application" - "concat_css:app" - "jade" - "copy:beta_index" - "preprocess" - "copy:main" - "copy:beta" - "dist_beta" - "rename:assets" - "rename:html" - ] - - grunt.registerTask "deploy_static_to_dev", [ - "dist" - "shell:deploy_static_to_dev" - "shell:deploy_start_dev" - ] - - grunt.registerTask "deploy_server_to_dev", [ - "shell:deploy_server_to_dev" - "shell:deploy_start_dev" - ] - - grunt.registerTask "deploy_beta_to_dev", [ - "shell:deploy_beta_to_dev" - "shell:deploy_start_dev" - ] - - grunt.registerTask "deploy_to_dev", [ - "dist" - "shell:deploy_static_to_dev" - "shell:deploy_beta_to_dev" - "shell:deploy_server_to_dev" - "shell:deploy_start_dev" - ] - - grunt.registerTask "deploy_static_to_staging", [ - "dist" - "shell:deploy_static_to_staging" - "shell:deploy_start_staging" - ] - - grunt.registerTask "deploy_server_to_staging", [ - "shell:deploy_server_to_staging" - "shell:deploy_start_staging" - ] - - grunt.registerTask "deploy_beta_to_staging", [ - "shell:deploy_beta_to_staging" - "shell:deploy_start_staging" - ] - - grunt.registerTask "deploy_to_staging", [ - "dist" - "shell:deploy_static_to_staging" - "shell:deploy_beta_to_staging" - "shell:deploy_server_to_staging" - "shell:deploy_start_staging" - ] - - grunt.registerTask "deploy_static_to_alpha", [ - "dist" - "shell:deploy_static_to_alpha" - "shell:deploy_start_alpha" - ] - - grunt.registerTask "deploy_server_to_alpha", [ - "shell:deploy_server_to_alpha" - "shell:deploy_start_alpha" - ] - - grunt.registerTask "deploy_beta_to_alpha", [ - "shell:deploy_beta_to_alpha" - "shell:deploy_start_alpha" - ] - - grunt.registerTask "deploy_to_alpha", [ - "dist" - "shell:deploy_static_to_alpha" - "shell:deploy_beta_to_alpha" - "shell:deploy_server_to_alpha" - "shell:deploy_start_alpha" - ] + grunt.registerTask "dist", (rootUrl, port) => + grunt.task.run [ + "shell:clean_bower_and_npm_cache" + "clean" + "shell:npm_install_dependencies" + "build" + ] + + if rootUrl + @rootUrl = rootUrl + + if port + @rootUrl += ":" + port + + console.log("Custom root URL: " + @rootUrl) + + grunt.task.run [ + "replace:root_url" + "replace:web_socket_url" + ] + + grunt.task.run [ + "shell:check_dependencies" + "shell:npm_install_dependencies" + "shell:bower_install_dependencies" + "shell:check_pgp_signatures" + "concat:application_dependencies" + "uglify:application_dependencies" + "concat:application" + "concat_css:app" + "jade" + "preprocess" + "copy:main" + "rename:assets" + "rename:html" + "git_changelog" + ] + + grunt.registerTask "dist_unsafe", (rootUrl, port) => + grunt.task.run [ + "shell:clean_bower_and_npm_cache" + "clean" + "shell:npm_install_dependencies" + "build" + ] + + if rootUrl + @rootUrl = rootUrl + + if port + @rootUrl += ":" + port + + console.log("Custom root URL: " + @rootUrl) + + grunt.task.run [ + "replace:root_url" + "replace:web_socket_url" + ] + + grunt.task.run [ + "shell:skip_check_dependencies" + "concat:application_dependencies" + "uglify:application_dependencies" + "concat:application" + "concat_css:app" + "jade" + "preprocess" + "copy:main" + "rename:assets" + "rename:html" + ] grunt.registerTask "check_translations", [ "shell:check_translations" diff --git a/README.md b/README.md index 5ae6127390..c7390a00d5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MyWallet V3 Frontend [![Build Status](https://travis-ci.org/blockchain/My-Wallet-V3-Frontend.png?branch=master)](https://travis-ci.org/blockchain/My-Wallet-V3-Frontend) [![Coverage Status](https://coveralls.io/repos/blockchain/My-Wallet-V3-Frontend/badge.svg?branch=master&service=github)](https://coveralls.io/github/blockchain/My-Wallet-V3-Frontend?branch=master) -An AngularJS bitcoin web wallet powered by [My-Wallet-V3](https://github.com/blockchain/My-Wallet-V3). +An AngularJS bitcoin web wallet powered by [My-Wallet-V3](https://github.com/blockchain/My-Wallet-V3). This is the new and improved wallet. You can see it at [alpha.blockchain.info](https://alpha.blockchain.info/). For the original wallet at [blockchain.info](https://blockchain.info/) please see [this repository](https://github.com/blockchain/My-Wallet) or [contact support](http://blockchain.zendesk.com/). @@ -23,6 +23,12 @@ Install dependencies: npm install ``` +Create a file called `.env` in the root of the project. Put the following in it: + +``` +ROOT_URL=https://blockchain.info +``` + ## Build Grunt watches and compiles the Jade view templates and CSS. Keep it running: @@ -49,7 +55,7 @@ npm start Visit [local.blockchain.com:8080](http://local.blockchain.com:8080/). Do not use `localhost:8080`. You will need to modify your "hosts" file (`/etc/hosts` on OSX and most UNIX systems) because this is no longer registered at the DNS level for application security reasons. Add this line to `/etc/hosts`: 127.0.0.1 local.blockchain.com - + ## Developing My-Wallet-V3 If you are making changes to [My-Wallet-V3](https://github.com/blockchain/My-Wallet-V3) that you want to try out in the frontend, create a symlink: @@ -58,21 +64,6 @@ rm My-Wallet-V3-Frontend/bower_components/blockchain-wallet/dist/my-wallet.js ln -s ../../../../My-Wallet-V3/dist/my-wallet.js My-Wallet-V3-Frontend/bower_components/blockchain-wallet/dist/my-wallet.js ``` -## Use Beta Invites Locally - -To enable the beta invite functionality, create a file called `.env` and add the following to it: -`INVITE=1` -`BETA_DATABASE_PATH=betakeys.MDF` -`ADMIN_PASSWORD=...` - -Copy the database file template (`betakeys.MDF` is ignored by git): -```sh -cp betakeys-template.MDF betakeys.MDF -``` - -You should see a number of example users at: -http://local.blockchain.com:8080/betaadmin/ - ## Usage You can open any wallet registered with your email address. It will ask you to upgrade to HD if needed. You can also sign up for a new wallet. @@ -90,31 +81,6 @@ If you enable "handle bitcoin links" in your wallet settings, you can open bitco bitcoin:?address=1FeerpCgswvGRLVKme759C96DUBtf7SvA2?amount=0.01 -## UI Tests - -Protractor UI tests are currently running on https://dev.blockchain.info/ or http://local.blockchain.com. Choose instance in util.js. File with login credentials (ignore.js) will be distributed separately and placed in the `e2e-tests` folder. - -Install Protractor globally: - - npm install -g protractor - -This installs both protractor and webdriver-manager. Update webdriver-manager: - - webdriver-manager update - -Start up a server: - - webdriver-manager start - -Open a new Terminal tab, navigate to the e2e test folder, and begin the tests: - - cd e2e-tests/ - protractor config.js - -To test specific files - - protractor config.js --specs [folder-name]/[file_name]_spec.js - ## Contribute Did you know you can [sign your commits](https://git-scm.com/book/tr/v2/Git-Tools-Signing-Your-Work) using a PGP key? diff --git a/app/admin.jade b/app/admin.jade deleted file mode 100644 index ee5a33aec0..0000000000 --- a/app/admin.jade +++ /dev/null @@ -1,271 +0,0 @@ -doctype -html.overflow-scroll(ng-app="AdminInterface" lang='en') - head - meta(charset='utf-8') - title Beta Admin - - - script(src='../bower_components/jquery/dist/jquery.js') - script(src='../bower_components/angular/angular.js') - script(src="../bower_components/bootstrap/dist/js/bootstrap.min.js") - script(src='../bower_components/angular-bootstrap/ui-bootstrap-tpls.js') - - link(rel='stylesheet', href='../build/css/blockchain.css') - link(rel='stylesheet', href='../build/css/navigation.css') - - script(src='../app/admin.js') - - - - - body.overflow-scroll(ng-controller="AdminCtrl") - .navbar.navbar-default.navbar-inverse.navbar-unauth(role='navigation') - .container-fluid - .navbar-header - a.navbar-brand - img#logo(src="../img/logo-updated.png" alt="Blockchain") - .navbar-collapse.collapse - ul.nav.navbar-nav.navbar-right - li.item - a(href="/") Home - li.item - a(href="/") About - li.item.active - a(href="/") Wallet - li.item - a(href="/") Explorer - li.item - a(href="/") Merchant - li.item - a(href="/") Support - - div.container.login-form.mbl - h1 V3 Beta Admin Interface - .form-group.col-xs-6 - form.form-inline - button.btn.btn-default(ng-click="openModal('assignKeyModal.html', 'AssignKeyCtrl')") + New Key - button.btn.btn-default(ng-click="openModal('capturePageModal.html', 'CapturePageCtrl')") Capture Page - button.btn.btn-default(ng-click="openModal('activationModal.html', 'ActivateKeysCtrl')") - span.glyphicon.glyphicon-flash - span  Activate Keys - button.btn.btn-default(ng-click="retrieveCSV()") CSV - .form-group.col-xs-6 - form.form-inline.pull-right(name="searchForm" ng-submit="load()" novalidate) - input.form-control(ng-model="search.text" type="text" placeholder="Search" required) - select.form-control(ng-model="search.filter" ng-options="f for f in filters" required) - button.btn.btn-default(ng-disabled="searchForm.$invalid" type="submit") - span(class="glyphicon glyphicon-arrow-right") - .form-group.col-xs-6.ng-cloak - label {{ walletCount }} Wallets Created - .table-div.ng-cloak - table.table.table-hover.table-condensed - tr - th.pointer(ng-repeat="th in headers" ng-click="sort(th)") {{ th }} - th ios - th android - th edit - th revoke - tr(ng-repeat="item in tableData") - td {{ item.rowid }} - td {{ item.key }} - td {{ item.name }} - td {{ item.email }} - td {{ getDateFromTime(item.lastseen) }} - td {{ getDateFromTime(item.email_opened_at) }} - td {{ getDateFromTime(item.email_link_followed_at) }} - td {{ item.guid }} - td {{ (item.activated) ? 'Activated' : 'Pending' }} - td {{ displayIOSStatus(item) }} - td {{ displayAndroidStatus(item) }} - td - a(ng-click="openModal('editModal.html', 'EditKeyCtrl', item)") edit - td - a(ng-click="revokeKey(item.rowid)") revoke - .flex.flex-justify.pal - button.btn.btn-primary.mrl(ng-click="load(-1)") - | Prev Page - button.btn.btn-primary.mll(ng-click="load(1)") - | Next Page - - // Assign new key modal - script(type="text/ng-template" id="assignKeyModal.html"). - - - - // Edit/Activate key modal - script(type="text/ng-template" id="editModal.html"). - - - - // Capture page settings modal - script(type="text/ng-template" id="capturePageModal.html"). - - - - // Activate many keys modal - script(type="text/ng-template" id="activationModal.html"). - - diff --git a/app/admin.js b/app/admin.js deleted file mode 100644 index 31049c3670..0000000000 --- a/app/admin.js +++ /dev/null @@ -1,204 +0,0 @@ -var admin = angular.module('AdminInterface', ['ui.bootstrap']); - -// Main Controller -admin.controller('AdminCtrl', function ($rootScope, $scope, $http, $modal, InterfaceHelper) { - - $rootScope.percentRequested = 0; - - // Declare scope variables - $scope.tableData = []; - $scope.headers = ['rowid', 'key', 'name', 'email', 'lastseen', 'email opened','link followed', 'guid', 'status']; - $scope.filters = ['name', 'email', 'key', 'guid']; - $scope.search = { text: '', filter: '', sort: 'rowid', order: 'Z' }; - $scope.offset = 0; - - // Convert timestamp to readable - $scope.getDateFromTime = function (time) { - return (time) ? new Date(time).toDateString() : 'Never'; - }; - - $scope.displayIOSStatus = function (item) { - if (item.ios_sent) return 'Sent'; - if (item.ios_approved) return 'Approved'; - if (item.ios) return 'Requested'; - return 'Not Requested'; - }; - - $scope.displayAndroidStatus = function (item) { - if (item.android_sent) return 'Sent'; - if (item.android_approved) return 'Approved'; - if (item.android) return 'Requested'; - return 'Not Requested'; - }; - - $scope.sort = function (header) { - if ($scope.search.sort === header) { - $scope.search.order = ($scope.search.order === 'A') ? 'Z' : 'A'; - } else { - $scope.search.sort = header; - $scope.search.order = 'A'; - } - $scope.load(); - }; - - // API functions - - $scope.revokeKey = function (id) { - InterfaceHelper.callApi('/delete-key', { rowid: id }) - .success($scope.load); - }; - - $scope.getPercent = function () { - $http.get('/percent_requested') - .success(function(res){ $rootScope.percentRequested = res.width; }) - .error(InterfaceHelper.error); - }; - - $scope.setPercent = function (value) { - InterfaceHelper.callApi('/set-percent-requested', {percent:value}) - .success($scope.getPercent); - }; - - $scope.getNumWalletsCreated = function () { - InterfaceHelper.callApi('/wallets-created') - .success(function (data) { - $scope.walletCount = data.count - }); - }; - - $scope.retrieveCSV = function () { - location.assign(InterfaceHelper.getRootUrl() + '/get-csv'); - }; - - $scope.load = function (offset) { - var filter = {}; - $scope.offset = $scope.offset + (offset|0); - if ($scope.offset < 0) $scope.offset = 0; - filter[$scope.search.filter] = $scope.search.text; - InterfaceHelper.callApi('/get-sorted-keys', { - sort: $scope.search.sort, - order: $scope.search.order, - filter: filter, - offset: 100 * $scope.offset - }).success(function (response) { - $scope.tableData = response.data; - }); - }; - - // Modal opening - $scope.openModal = function (tmpl, ctrl, entry) { - $modal.open({ - templateUrl: tmpl, - controller: ctrl, - resolve: { - getPercent: function () { return $scope.getPercent; }, - setPercent: function () { return $scope.setPercent; }, - load: function () { return $scope.load; }, - entry: function () { return entry; } - } - }); - }; - - // Initial data load - $scope.load(); - $scope.getNumWalletsCreated(); -}); - -// Modal Controllers -admin.controller('AssignKeyCtrl', function ($scope, $uibModalInstance, InterfaceHelper, load) { - $scope.fields = { name: '', email: '', guid: '' }; - $scope.assignKey = function (name, email, guid) { - if (guid === '') guid = null; - InterfaceHelper.callApi('/assign-key', {name:name,email:email,guid:guid}) - .success(load); - $uibModalInstance.dismiss(); - }; -}); - -admin.controller('CapturePageCtrl', function ($scope, getPercent, setPercent) { - $scope.setPercent = setPercent; - getPercent(); -}); - -admin.controller('EditKeyCtrl', function ($scope, $uibModalInstance, InterfaceHelper, load, entry) { - $scope.fields = angular.copy(entry); - $scope.submitEdit = function (doActivate) { - var endpoint = (doActivate) ? '/activate-key' : '/update-key'; - var selection = { rowid: entry.rowid }; - var update = InterfaceHelper.compareProperties($scope.fields, entry); - update.activated = doActivate; - InterfaceHelper.callApi(endpoint, {selection: selection, update: update}) - .success(function () { - load(); - $uibModalInstance.dismiss(); - }); - }; - $scope.resendText = 'Resend Invitation Email' - $scope.resending = false; - $scope.resendActivationEmail = function () { - $scope.resending = true; $scope.resendText = 'Sending...'; - InterfaceHelper.callApi('/resend-activation', {key: $scope.fields.key}) - .success(function(res){ - if (res.error) console.error(res.error); - $scope.resendText = res.error ? 'Error' : 'Sent!'; - }); - }; -}); - -admin.controller('ActivateKeysCtrl', function ($scope, InterfaceHelper, load) { - $scope.step = 0; - $scope.numKeys = $scope.numEmails = 0; - $scope.activate = function (min, max) { - $scope.step = 1; - InterfaceHelper.callApi('/activate-all', {min:min||null,max:max||null}) - .success(function (res) { - load(); - if (res.error) $scope.error = res.error; - if (typeof res.data === 'object') { - $scope.numKeys = res.data.count; - $scope.numEmails = res.data.successful; - } - $scope.step = 2; - }); - }; - $scope.resendText = 'Resend Invitation Emails' - $scope.resend = function (min, max) { - $scope.step = 1; - InterfaceHelper.callApi('/resend-many', {min:min||null,max:max||null}) - .success(function(res){ - if (res.error) $scope.error = res.error; - if (typeof res.data === 'object') { - $scope.numKeys = res.data.count; - $scope.numEmails = res.data.successful; - } - $scope.step = 3; - }); - }; -}); - -// Helper Service -admin.factory('InterfaceHelper', function ($http, $httpParamSerializerJQLike) { - var helper = {}; - var rootUrl = '/admin/api'; - helper.error = function (response) { - if (!response || !response.error) return; - console.error(response.error) - }; - helper.callApi = function (endpoint, data) { - return $http.get(rootUrl + endpoint, { - params: data, - paramSerializer: $httpParamSerializerJQLike - }).error(helper.error); - }; - helper.getRootUrl = function () { - return rootUrl; - }; - helper.compareProperties = function (o1, o2) { - var object = {}; - for (p in o1) { - if (o1[p] !== o2[p]) object[p] = o1[p]; - } - return object; - }; - return helper; -}); diff --git a/app/index.jade b/app/index.jade index 16a4154fcc..1c12104e90 100644 --- a/app/index.jade +++ b/app/index.jade @@ -1,7 +1,6 @@ doctype - - - + + head meta(charset='utf-8') meta(name="viewport",content="width=device-width, initial-scale=1.0, maximum-scale=1") @@ -56,6 +55,7 @@ head script(src='build/js/core/store.service.js') script(src='build/js/core/myWallet.service.js') script(src='build/js/core/payment.service.js') + script(src='build/js/core/walletTokenEndpoints.service.js') script(src='build/js/browser-polyfill.js') script(src='build/js/app.js') @@ -69,7 +69,6 @@ head script(src='build/js/controllers/claimModal.controller.js') script(src='build/js/controllers/confirmRecoveryPhrase.controller.js') script(src='build/js/controllers/home.controller.js') - script(src='build/js/controllers/feedback.controller.js') script(src='build/js/controllers/firstTime.controller.js') script(src='build/js/controllers/login.controller.js') script(src='build/js/controllers/navigation.controller.js') @@ -86,7 +85,13 @@ head script(src='build/js/controllers/modalNotification.controller.js') script(src='build/js/controllers/recoverFunds.controller.js') script(src='build/js/controllers/lostGuid.controller.js') + script(src='build/js/controllers/ResetTwoFactor.controller.js') + script(src='build/js/controllers/feedback.controller.js') script(src='build/js/controllers/walletNavigation.controller.js') + script(src='build/js/controllers/verifyEmail.controller.js') + script(src='build/js/controllers/unsubscribe.controller.js') + script(src='build/js/controllers/authorizeApprove.controller.js') + script(src='build/js/controllers/resetTwoFactorToken.controller.js') script(src='build/js/controllers/settings/settings.controller.js') script(src='build/js/controllers/settings/info.controller.js') script(src='build/js/controllers/settings/preferences.controller.js') @@ -165,8 +170,8 @@ head //- Whitelist by copying script from browser and putting it in a file diff --git a/app/partials/alpha-agreement.jade b/app/partials/alpha-agreement.jade deleted file mode 100644 index f2dfe127b9..0000000000 --- a/app/partials/alpha-agreement.jade +++ /dev/null @@ -1,112 +0,0 @@ -.modal-header.bc-modal-header - h3 - | ALPHA PROGRAM PARTICIPATION AGREEMENT -.modal-body - .flex-column.pal - p - | This Alpha Program Participation Agreement (this “Agreement”) is made and entered into by and between Blockchain Lux S.a.r.l. or its affiliates (“Blockchain”), and you, the “User” who uses the Alpha Services as hereinafter defined. The terms of any other agreement between Blockchain and User are hereby incorporated into the terms of this Agreement, however, in the case of any conflict, the terms of this Agreement supersede and control, except where otherwise specifically stated herein. This Agreement describes the terms and conditions under which User may access and use certain features, technologies and services that are not yet generally commercially available (each, a “Alpha Service”). Blockchain and User are sometimes referred to collectively as the “Parties” and each individually as a “Party.” - p - b THE SOFTWARE SUPPLIED TO USER IS IN ALPHA FORM AND SUPPLIED FOR TESTING PURPOSES ONLY. BUGS, CRASHES AND OTHER MALFUNCTIONS ARE ALMOST CERTAIN TO OCCUR. UNDER NO CIRCUMSTANCES MAY USER USE THE SOFTWARE SUPPLIED IN CONNECTION WITH THE ALPHA SERVICE TO SECURE MORE THAN $10 WORTH OF BITCOIN. - p - b ANY DISPUTES ARISING OUT OF THIS AGREEMENT WILL BE RESOLVED BY BINDING, INDIVIDUAL ARBITRATION AND THE PARTIES WAIVE THEIR RIGHTS TO GO TO COURT OR PARTICIPATE IN A CLASS ACTION LAWSUIT OR CLASS- WIDE ARBITRATION. - .bg-light-blue.flex-column.mtl.pal - h2 1. Definitions - p - ol - li - | “Alpha Materials” means any hardware, software, specifications or other technical documentation related to a specific Alpha Service that may be provided to User by Blockchain. - li - | “Alpha Use” means the testing and evaluation of a specific Alpha Service by User and certain other Blockchain customers or business partners. - li - | “Alpha Use Information” means all information relating to User’s use, testing or evaluation of a Alpha Service or any related Alpha Materials, including all observations or information regarding the nature, quality, performance, features or functionality of a Alpha Service or any related Alpha Materials. - li - | “Feedback” means all feedback, suggestions, and ideas that User provides to Blockchain concerning improvements or enhancements to a Alpha Service or any related Alpha Materials. - li - | “Confidential Information” means all nonpublic information disclosed by Blockchain or its agents to User, its affiliates, or the agents of any of the foregoing, that is designated as confidential or that, given the nature of the information or the circumstances surrounding its disclosure, reasonably should be considered as confidential. Confidential Information includes, without limitation (a) nonpublic information relating to Blockchain’ or its affiliates’ technology, customers, business plans, promotional and marketing activities, finances and other business affairs, (b) third-party information that Blockchain or its affiliates is obligated to keep confidential, (c) Alpha Materials, Alpha Use Information, Feedback, or any other information about or involving (including the existence of) any of the Alpha Uses or Alpha Services, and (d) the nature, content and existence of this Agreement and any discussions or negotiations between the Parties. Confidential Information does not include any information that (i) is or becomes publicly available without breach of this Agreement, (ii) can be shown by documentation to have been known to the receiving party at the time of its receipt from the disclosing party, (iii) is received from a third party who did not acquire or disclose such information by a wrongful or tortious act, or (iv) can be shown by documentation to have been independently developed by the receiving party without reference to any Confidential Information. - li - | “Policies” means all policies and guidelines related to any Alpha Service, Alpha Materials or other web services offered by Blockchain or its affiliates and made available to User, including privacy policies, terms of use, acceptable use policies, and any additional terms and conditions for a specific Alpha Use. - .flex-column.mtl.pal - h2 2. Participation in Alpha Program - p - ul - li - | 2.1. Generally. Blockchain grants User a limited, nonexclusive, non- transferable, royalty- free, revocable license to do the following during the term of the applicable Alpha Use: (a) access and use the Alpha Service solely for internal evaluation purposes; and (b) install, copy, and use any related Alpha Materials solely as necessary to access and use the Alpha Service in the manner permitted by this Agreement. After the conclusion of a Alpha Use, User will not have any further right to use the applicable Alpha Service, and if Blockchain releases a generally-available version of the Alpha Service, User’s use of the generally commercially available version will be subject to separate terms and conditions. However, Blockchain does not guarantee that any Alpha Service will ever be made generally commercially available, or that any generally commercially available version will contain the same or similar functionality as the version made available by Blockchain during the Alpha Use. - li - | 2.2. Blockchain reserves the right to amend this agreement at its discretion and for any reason by giving 30 days’ notice to User, after which time the amendment will become effective to bind the parties. - li - | 2.3. Restrictions and Limitations. User will not: - ul - li - | i. allow access to any Alpha Service or Alpha Materials by any third party other than User’s employees and contractors who (i) have a need to use or access the Alpha Service or Alpha Materials in connection with User’s internal evaluation activities and (ii) have executed written nondisclosure agreements obligating them to protect the confidentiality of the Alpha Service and Alpha Materials; - li - | ii. violate any usage limits for a Alpha Service that Blockchain may communicate to User; - li - | iii. export or allow access to any Alpha Service or Alpha Materials in any manner contrary to the export regulations of the United States; or - li - | iv. otherwise access or use any Alpha Service, or install, copy or use any Alpha Materials, in any manner or for any purpose not expressly permitted by this Agreement. - - | Blockchain may modify the permitted use of or suspend User’s access to any Alpha Service at any time and for any reason. Alpha Services also may be unavailable or their performance may be negatively affected by scheduled maintenance. No service levels or other uptime guarantees apply to the Alpha Services. Blockchain may or may not, at its discretion, notify User in advance of scheduled maintenance, but Blockchain is unable to provide advance notice of unscheduled or emergency maintenance. - li - | 2.4. Alpha Use Information and Feedback. Alpha Use Information and Feedback. In consideration of the rights granted in this Agreement, User will provide Alpha Use Information, when and in the form reasonably requested by Blockchain. Blockchain will have a perpetual and irrevocable right to use, evaluate and otherwise exploit all Alpha Use Information for its own purposes. User will not use any Alpha Use Information except for its internal evaluation purposes. Blockchain has a perpetual and irrevocable right to use and exploit all Feedback and may use the Feedback without accounting or compensation to User. User will not provide any Alpha Use Information or Feedback unless it has all rights necessary to do so. - .bg-light-blue.flex-column.mtl.pal - h2 3. Term and Termination - p - ul - li - | 3.1. Term. Blockchain may, at its discretion, specify the term of each individual Alpha Use, but the term will automatically terminate upon the release of a generally commercially available version of the applicable Alpha Service. The term of this Agreement will commence on the date you begin to use the Alpha Service and will continue until terminated pursuant to Section 3.2. - li - | 3.2. Termination. Either Party may terminate User’s participation in an individual Alpha Use, or this Agreement entirely, at any time for any reason upon notice to the other Party. Upon termination of this Agreement: (a) all rights and licenses granted to User in this Agreement will immediately terminate; (b) User will immediately return or, if instructed by Blockchain, destroy all Alpha Materials or any other confidential or proprietary information of Blockchain or its affiliates related to any Alpha Service or this Agreement; and (c) Sections 2.4 and 4 through 8 will survive. - .flex-column.mtl.pal - h2 4. Confidentiality - p - ul - li - | 4.1. Use and Disclosure. User may not disclose any Confidential Information during the term of this Agreement or at any time during the three (3) year period following the end of the Term. If the Parties have executed a separate non-disclosure agreement (the “NDA”) and there is a conflict between the terms of the NDA and the terms of this Section 4.1, the terms of the NDA will control. - li - | 4.2. Publicity. Neither Party will issue any press release or public statement regarding this Agreement or any Alpha Use, Alpha Service or Alpha Materials unless Blockchain has approved in writing the time, form and content of the information to be disseminated to third parties or the public. - .bg-light-blue.flex-column.mtl.pal - h2 5. DISCLAIMER OF WARRANTIES - p - ul - li - | 5.1. THE ALPHA SERVICES AND ALPHA MATERIALS ARE NOT READY FOR GENERAL COMMERCIAL RELEASE AND MAY CONTAIN BUGS, ERRORS, DEFECTS OR HARMFUL COMPONENTS. ACCORDINGLY, BLOCKCHAIN IS PROVIDING THE ALPHA SERVICES AND ALPHA MATERIALS TO USER - b “AS IS.” - | BLOCKCHAIN MAKES NO WARRANTIES OF ANY KIND WITH RESPECT TO THE ALPHA SERVICES OR ALPHA MATERIALS, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. NOTWITHSTANDING ANY PUBLISHED MATERIALS THAT STATE OTHERWISE, BLOCKCHAIN DOES NOT WARRANT THAT THE ALPHA SERVICES OR ALPHA MATERIALS WILL BE ERROR-FREE - | OR THAT THEY WILL MEET ANY SPECIFIED SERVICE LEVEL, OR WILL OPERATE WITHOUT INTERRUPTIONS OR DOWNTIME. - .flex-column.mtl.pal - h2 6. LIMITATION OF LIABILITY - p - ul - li - | 6.1. NEITHER BLOCKCHAIN NOR ANY OF ITS AFFILIATES OR LICENSORS WILL BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES ARISING FROM OR RELATED TO THIS AGREEMENT, WHETHER IN AN ACTION IN CONTRACT, TORT (INCLUDING NEGLIGENCE) OR OTHERWISE, EVEN IF THE PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. IN ANY CASE, THE AGGREGATE LIABILITY OF BLOCKCHAIN AND ITS AFFILIATES AND LICENSORS ARISING FROM OR RELATED TO THIS AGREEMENT WILL NOT EXCEED THE AMOUNTS PAID (IF ANY) BY USER TO BLOCKCHAIN UNDER THIS AGREEMENT. - .bg-light-blue.flex-column.mtl.pal - h2 7. Disputes Resolution by Binding Arbitration; Jury Trial Waiver; Class Action Waiver. - p - ul - li - | 7.1. For any and all controversies, disputes, demands, claims, or causes of action between the parties (including the interpretation and scope of this Section and the arbitrability of the controversy, dispute, demand, claim, or cause of action) relating to the Alpha Service, Alpha Use or this Agreement (as well as any related or prior agreement), the Parties agree to resolve any such controversy, dispute, demand, claim, or cause of action exclusively through binding and confidential arbitration. The arbitration will take place in the federal judicial district of the User’s residence if the User resides in the United States, or in New York City if the User does not. As used in this Section, “we” and “us” mean Blockchain. In addition, “we” and “us” include any third party providing any product, service, or benefit in connection with the Alpha Services or this Agreement (as well as any related or prior agreement that you may have had with us) if such third party is named as a co-party with us in any controversy, dispute, demand, claim, or cause of action subject to this Section. - li - | 7.2. Arbitration will be subject to the Federal Arbitration Act and not any state arbitration law. The arbitration will be conducted before one commercial arbitrator from the American Arbitration Association (“AAA”) with substantial experience in resolving commercial contract disputes. As modified by this Agreement, and unless otherwise agreed upon by the parties in writing, the arbitration will be governed by the AAA’s Commercial Arbitration Rules and, if the arbitrator deems them applicable, the Supplementary Procedures for Consumer Related Disputes (collectively, the “Rules and Procedures”). Where no claims or counterclaims exceed $10,000, the dispute will be resolved by the submission of documents without a hearing, unless a hearing is - | requested by a party or deemed necessary by the arbitrator, in which case, a party may elect to participate telephonically. - li - | 7.3. You should review this provision carefully. To the extent permitted by applicable law, - b YOU ARE GIVING UP YOUR RIGHT TO GO TO COURT - | to assert or defend your rights EXCEPT for matters that you file in small claims court in the state or municipality of your residence within the jurisdictional limits of the small claims court and as long as such matter is only pending in that court. Additionally, notwithstanding this agreement to arbitrate, claims of defamation, and infringement or misappropriation of the other party’s patent, copyright, trademark, or trade secret shall not be subject to this arbitration agreement. Such claims shall be exclusively brought in the state or federal courts located in New York County, New York. Additionally, notwithstanding this agreement to arbitrate, you or we may seek emergency equitable relief before the state or federal courts located in New York County, New York in order to maintain the status quo pending arbitration and hereby agree to submit to the exclusive personal jurisdiction of the courts located within New York County, New York for such purpose. A request for interim measures shall not be deemed a waiver of the right to arbitrate. - li - | 7.4. Your rights will be determined by a NEUTRAL ARBITRATOR and NOT a judge or jury. You are entitled to a FAIR HEARING, BUT the arbitration procedures may be SIMPLER AND MORE LIMITED THAN RULES APPLICABLE IN COURT. Arbitrators’ decisions are as enforceable as any court order and are subject to VERY LIMITED REVIEW BY A COURT. - li - | 7.5. You and we must abide by the following rules: (A) ANY CLAIMS BROUGHT BY YOU OR US MUST BE BROUGHT IN THE PARTY’S INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING; (B) THE ARBITRATOR MAY NOT CONSOLIDATE MORE THAN ONE PERSON’S CLAIMS, MAY NOT OTHERWISE PRESIDE OVER ANY FORM OF A REPRESENTATIVE OR CLASS PROCEEDING, AND MAY NOT AWARD CLASS-WIDE RELIEF; (c) in the event that you are able to demonstrate that the costs of arbitration will be prohibitive as compared to the costs of litigation, we will pay as much of your filing and hearing fees in connection with the arbitration as the arbitrator deems necessary to prevent the arbitration from being cost- prohibitive as compared to the cost of litigation, (d) we also reserve the right, in our sole and exclusive discretion, to assume responsibility for any or all of the costs of the arbitration; (e) the arbitrator will honor claims of privilege and privacy recognized at law; (f) the arbitration will be confidential, and neither you nor we may disclose the existence, content, or results of any arbitration, except as may be required by applicable law or for purposes of enforcement of the arbitration award; (g) subject to the limitation of liability provisions of these Terms, the arbitrator may award any individual relief or - | individual remedies that are expressly permitted by applicable law; and (h) you and we will pay our respective attorneys’ fees and expenses, unless there is a statutory provision that requires the prevailing party to be paid its fees and litigation expenses and the arbitrator awards such attorneys’ fees and expenses to the prevailing party, and, in such instance, the fees and costs awarded will be determined by the applicable law. - li - | 7.7. This Section will survive termination of your account and this Agreement as well as any voluntary payment of any debt in full by you or any bankruptcy by you or us. With the exception of subparts (a) and (b) above of this Section (prohibiting arbitration on a class or collective basis), if any part of this arbitration provision is deemed to be invalid, unenforceable, or illegal, or otherwise conflicts with the Rules and Procedures, then the balance of this arbitration provision will remain in effect and will be construed in accordance with its terms as if the invalid, unenforceable, illegal or conflicting part was not contained herein. If, however, either subpart (a) or (b) above of this Section is found to be invalid, unenforceable, or illegal, then the entirety of this arbitration provision will be null and void, and neither you nor we will be entitled to arbitration. If for any reason a claim proceeds in court rather than in arbitration, the dispute shall be exclusively brought in state or federal court located in New York County, New York. - li - | 7.8. YOU AGREE THAT, TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, ANY CLAIM OR CAUSE OF ACTION ARISING OUT OF OR RELATING TO THE SERVICE OR THESE TERMS MUST BE FILED WITHIN ONE (1) YEAR AFTER SUCH CLAIM OR CAUSE OF ACTION AROSE OR IT WILL BE FOREVER BARRED. - .flex-column.mtl.pal - h2 8. Miscellaneous - p - ul - li - | 8.1. Except for those limited rights expressly granted in Section 2.1, Blockchain and its licensors retain all right, title and interest in and to the Alpha Services and the Alpha Materials, including all related intellectual property rights. The parties are independent contractors with respect to each other, and nothing in this Agreement shall be construed as creating an employer-employee relationship, a partnership, agency relationship or a joint venture between the parties. This Agreement further controls the actions of all party representatives, officers, agents, employees and associated individuals. The terms of this Agreement shall be binding on the parties, and all successors to the foregoing who take their rights hereunder. Neither party will assign, transfer or delegate its rights or obligations under this Agreement (in whole or in part) without the other party’s prior - | written consent except that Blockchain may assign and delegate this Agreement pursuant to a transfer of all or substantially all of Blockchain’s business and assets, whether by merger, sale of assets, sale of stock, or otherwise. Any attempted assignment, transfer or delegation in violation of the foregoing shall be null and void. All modifications to and waivers of any terms of this Agreement must be in a writing that is signed by the parties hereto and expressly references this Agreement. This Agreement shall be governed by the laws of the State of New York, without regard to New York conflict of laws rules. The exclusive venue and jurisdiction for any and all disputes, claims and controversies arising from or relating to this Agreement shall be the state or federal courts located in New York City. In the event that any provision of this Agreement conflicts with governing law or if any provision is held to be null, void or otherwise ineffective or invalid by a court of competent jurisdiction, (a) such provision shall be deemed to be restated to reflect as nearly as possible the original intentions of the parties in accordance with applicable law, and (b) the remaining terms, provisions, covenants and restrictions of this Agreement shall remain in full force and effect. No waiver of any breach of any provision of this Agreement shall constitute a waiver of any prior, concurrent or subsequent breach of the same or any other provisions hereof, and no waiver shall be effective unless made in writing and signed by an authorized representative of the waiving party. This Agreement and all expressly referenced documents constitute the entire agreement between the parties with respect to the subject matter hereof and supersedes all prior and contemporaneous agreements or communications, including any reseller or similar agreements previously executed by the parties. All notices, consents and approvals under this Agreement must be delivered in writing by email to the email address supplied by one party to the other during the account signup process, or posted to the Blockchain website. -.modal-footer.flex-end.pal - button.button-success.button-lg(ng-click="acceptAgreement(); $dismiss()" translate="OK") diff --git a/app/partials/authorize-approve.jade b/app/partials/authorize-approve.jade new file mode 100644 index 0000000000..cbfb0e8ee8 --- /dev/null +++ b/app/partials/authorize-approve.jade @@ -0,0 +1,60 @@ +div(ng-show="checkingToken") + header + hgroup + .flex-between.flex-center.flex-wrap + h2.em-300.mtn(translate="CHECKING_LOGIN_ATTEMPT") + hgroup + img(src="img/spinner.gif") +div(ng-show="differentBrowser") + header + hgroup + .flex-between.flex-center.flex-wrap + h2.em-300.mtn(translate="AUTHORIZE_APPROVE_OTHER_BROWSER") + p.em-300(translate="AUTHORIZE_APPROVE_OTHER_BROWSER_EXPLAIN") + hgroup + p.em-300 {{ details.device_change_reason }} + + .row + .col-xs-6 + h4(translate="AUTHORIZE_APPROVE_REQUESTING_DEVICE") + p + b + span(translate="BROWSER") + span : + | {{ details.requester_device_description }} + br + b + span(translate="IP_ADDRESS") + span : + | {{ details.requester_ip }} + br + b + span(translate="COUNTRY_OF_ORIGIN") + span : + | {{ details.requester_country }} + br + .col-xs-6 + h4(translate="AUTHORIZE_APPROVE_THIS_DEVICE") + p + b + span(translate="BROWSER") + span : + | {{ details.approver_device_description }} + br + b + span(translate="IP_ADDRESS") + span : + | {{ details.approver_ip }} + br + b + span(translate="COUNTRY_OF_ORIGIN") + span : + | {{ details.approver_country }} + br + + form.ptl.form-horizontal.clearfix(role="form") + .flex-between.flex-center.pal + .flex-center + button.button-danger(ui-ladda="busyApproving", ng-click="approve()", data-style="expand-left", ladda-translate="ACCEPT") + .flex-center.flex-end + button.button-primary(ui-ladda="busyRejecting", ng-click="reject()", data-style="expand-left", ladda-translate="REJECT") diff --git a/app/partials/confirm-recovery-phrase-modal.jade b/app/partials/confirm-recovery-phrase-modal.jade index 11f54f1dae..9216bcf5e8 100644 --- a/app/partials/confirm-recovery-phrase-modal.jade +++ b/app/partials/confirm-recovery-phrase-modal.jade @@ -17,7 +17,7 @@ .row .col-xs-12 p.center - a.btn.btn-inverse(href="img/recovery.pdf", target="_blank") + a.btn.btn-inverse(href="img/recovery.pdf", target="_blank", download="recovery.pdf") span i.ti-printer.prm span(translate="PRINT_RECOVERY_SHEET") @@ -32,8 +32,7 @@ .row .col-xs-3(ng-repeat="word in recoveryPhrase.slice(offset, offset + 4)") p.center - b - {{ offset + $index + 1 }}. {{ word }} + b {{ offset + $index + 1 }}. {{ word }} .row .col-xs-12 p.center diff --git a/app/partials/first-login-modal.jade b/app/partials/first-login-modal.jade index bc53b9874b..ff9243ca66 100644 --- a/app/partials/first-login-modal.jade +++ b/app/partials/first-login-modal.jade @@ -1,6 +1,6 @@ .modal-body.flex .flex-column.flex-between.rocket-text.flex-1 - h3.mtn.h2.em-300.center-align(translate="LAUNCHED") - p.center-align.mtm.width-100.h4.em-300(translate="WELCOME_TEXT") - button.button-success.button-lg.mtl(ng-click="ok()", translate="GET_STARTED") + h3.mtn.h2.em-300.center-align(translate="FIRST_LOGIN_TITLE") + p.center-align.mtm.width-100.h4.em-300(translate="FIRST_LOGIN_TEXT") + button.button-success.button-lg.mtl(ng-click="ok()", translate="FIRST_LOGIN_ACTION") img.rocket-gif.flex-1(src="img/rocket.gif") diff --git a/app/partials/help.jade b/app/partials/help.jade index aeb7a4d768..295c2d9bf3 100644 --- a/app/partials/help.jade +++ b/app/partials/help.jade @@ -23,7 +23,7 @@ div h5.blue.em-300(translate="LOST_2FA") p.em-400(translate="LOST_2FA_DESC") .flex-1.flex-end - a.button-default.button-nowrap(translate="CONTACT_SUPPORT" href="https://blockchain.zendesk.com/anonymous_requests/new" target="_blank") + a.button-default.button-nowrap(translate="RESET_2FA" ui-sref="public.reset-two-factor") .flex-end - a.button-muted.mtm(ui-sref="public.login") + a.button-muted.mtm(ui-sref="public.login-no-uid") span(translate="GO_BACK") diff --git a/app/partials/home.jade b/app/partials/home.jade index 79531f04f3..7c00d49561 100644 --- a/app/partials/home.jade +++ b/app/partials/home.jade @@ -3,7 +3,7 @@ activity-feed .col-md-6.col-sm-12.col-xs-12.col-home.flex-column .section - h5.mtn.type-h5.em-400.mbl Account Balances + h5.mtn.type-h5.em-400.mbl(translate="BALANCES") ul.pln.account-balances li.flex-between.border-bottom-light(ng-repeat="account in activeAccounts") span.em-500 {{::account.label}} diff --git a/app/partials/lost-guid.jade b/app/partials/lost-guid.jade index a2adcf076e..053e691f02 100644 --- a/app/partials/lost-guid.jade +++ b/app/partials/lost-guid.jade @@ -41,5 +41,5 @@ .flex-end button.button-muted.mrm( type="button" - ui-sref="public.login" + ui-sref="public.login-no-uid" translate="CONTINUE_TO_LOGIN") diff --git a/app/partials/navigation.jade b/app/partials/navigation.jade index eb807724fc..469bbd6573 100644 --- a/app/partials/navigation.jade +++ b/app/partials/navigation.jade @@ -25,12 +25,12 @@ a(href="https://www.blockchain.info/wallet/bitcoin-faq", target="_blank", translate="WHATS_BITCOIN") li a(href="https://www.blockchain.info/", target="_blank", translate="BLOCKCHAIN_INFO") - li - a(href="https://www.blockchain.info/api", target="_blank", translate="API") li a(href="https://www.blockchain.info/charts", target="_blank",translate="CHARTS") li a(href="https://markets.blockchain.info/", target="_blank", translate="MARKETS") + li + a(href="https://www.blockchain.info/api", target="_blank", translate="API") li.item.visible-xs a(href="https://www.blockchain.info/wallet/bitcoin-faq", target="_blank",translate="WHATS_BITCOIN") li.item.visible-xs diff --git a/app/partials/public.jade b/app/partials/public.jade index 41cdc44b43..d952c6a692 100644 --- a/app/partials/public.jade +++ b/app/partials/public.jade @@ -2,7 +2,7 @@ .navbar.navbar-default.navbar-inverse.bc-header(role='navigation') .container-fluid .navbar-header.flex-between - a.navbar-brand(ng-click="visitTransactions()") + a.navbar-brand(ui-sref="wallet.common.home") img#logo.pll(src="img/logo-updated.png",alt="Blockchain") button.navbar-toggle(type="button", ng-init="navCollapsed = true", ng-click="navCollapsed = !navCollapsed") span.sr-only Toggle navigation @@ -10,17 +10,18 @@ .navbar-collapse.collapse(ng-class="{'in bg-blue' : !navCollapsed}", ng-click="navCollapsed=true") ul.nav.navbar-nav.navbar-right li.item - a.pam(href="https://www.blockchain.com", translate="HOME") + a.pam(href="https://www.blockchain.info", translate="EXPLORER") li.item - a.pam(href="https://blockchain.com/about", translate="ABOUT") + a.pam(href="https://www.blockchain.info/charts", translate="CHARTS") + li.item + a.pam(href="https://www.blockchain.info/api", translate="API") li.item.active a.pam(href="#", translate="WALLET") li.item - a.pam(href="https://www.blockchain.info", translate="EXPLORER") - li.item - a.pam(href="http://blockchain.com/our-products/blockchain-merchant/", translate="MERCHANT") + a.pam(href="https://blockchain.com/about", translate="ABOUT") li.item a.pam(href="https://blockchain.zendesk.com/", translate="SUPPORT") + .flex-center.flex-justify.flex-column .flex.flex-justify(ui-view="alerts") .login-box.mhs(ui-view="contents") diff --git a/app/partials/reset-two-factor-token.jade b/app/partials/reset-two-factor-token.jade new file mode 100644 index 0000000000..f7f306b955 --- /dev/null +++ b/app/partials/reset-two-factor-token.jade @@ -0,0 +1,7 @@ +div(ng-show="checkingToken") + header + hgroup + .flex-between.flex-center.flex-wrap + h2.em-300.mtn(translate="RESET_2FA_CHECKING") + hgroup + img(src="img/spinner.gif") diff --git a/app/partials/reset-two-factor.jade b/app/partials/reset-two-factor.jade new file mode 100644 index 0000000000..7585f9a3af --- /dev/null +++ b/app/partials/reset-two-factor.jade @@ -0,0 +1,80 @@ +.pos-rel(ng-switch="currentStep") + header.flex-center.flex-between + .flex-column + h2.em-300.mtn(translate="RESET_2FA") + p(translate="RESET_2FA_HELP_1", ng-switch-when="1") + p(translate="RESET_2FA_HELP_2", ng-switch-when="1") + form.ptl.form-horizontal(name="form" autocomplete="off" novalidate) + div(ng-switch-when="1") + .form-group(ng-class="{'has-error': form.uid.$invalid && form.uid.$touched}") + label(translate="RESET_2FA_WALLET_IDENTIFIER") + input.form-control( + name="uid" + ng-model="fields.uid" + required) + p.help-block + span(translate="RESET_2FA_WALLET_IDENTIFIER_EXPLAIN") + span + a(ui-sref="public.reminder", translate="LOOK_IT_UP_HERE") + span . + .form-group(ng-class="{'has-error': form.email.$invalid && form.email.$touched}") + label(translate="RESET_2FA_REGISTERED_EMAIL") + input.form-control( + type="email" + name="email" + ng-model="fields.email" + placeholder="{{ 'OPTIONAL' | translate }}") + p.help-block(translate="RESET_2FA_REGISTERED_EMAIL_EXPLAIN") + .form-group(ng-class="{'has-error': form.newEmail.$invalid && form.newEmail.$touched}") + label(translate="RESET_2FA_NEW_EMAIL") + input.form-control( + type="email" + name="newEmail" + ng-model="fields.newEmail" + placeholder="{{ 'OPTIONAL' | translate }}") + p.help-block(translate="RESET_2FA_NEW_EMAIL_EXPLAIN") + .form-group(ng-class="{'has-error': form.secret.$invalid && form.secret.$touched}") + label(translate="RESET_2FA_SECRET_PHRASE") + input.form-control( + name="secret" + ng-model="fields.secret" + placeholder="{{ 'OPTIONAL' | translate }}") + p.help-block(translate="RESET_2FA_SECRET_PHRASE_EXPLAIN") + .form-group(ng-class="{'has-error': form.message.$invalid && form.message.$touched}") + label(translate="RESET_2FA_MESSAGE") + textarea.form-control( + name="message" + ng-model="fields.message" + placeholder="{{ 'OPTIONAL' | translate }}") + p.help-block(translate="RESET_2FA_MESSAGE_EXPLAIN") + .form-group(ng-class="{'has-error': form.captcha.$invalid && form.captcha.$touched}") + label(translate="CAPTCHA") + p(translate="CAPTCHA_EXPAIN") + img.mbl(ng-src="{{captchaSrc}}") + input.form-control( + type="text" + name="captcha" + ng-model="fields.captcha" + required) + .flex-center.flex-end.mvl(ng-switch-when="1") + img(ng-show="working" src="img/spinner.gif") + button.button-muted.mrm( + type="button" + ng-disabled="working" + ui-sref="public.help" + translate="GO_BACK") + button.button-success( + type="submit" + ng-click="resetTwoFactor()" + ng-disabled="!form.$valid || working" + translate="CONTINUE") + .flex-center.flex-justify.flex-column(ng-switch-when="2") + .level-complete.flex-center.flex-justify + i.ti-check.bright-green + h4.em-300.mtl(translate="SUCCESS") + p.em-300(translate="RESET_2FA_SUCCESS") + .flex-end + button.button-muted.mrm( + type="button" + ui-sref="public.login-no-uid" + translate="CONTINUE_TO_LOGIN") diff --git a/app/partials/send.jade b/app/partials/send.jade index ce48cf4d1c..edc23b9216 100644 --- a/app/partials/send.jade +++ b/app/partials/send.jade @@ -34,12 +34,13 @@ ui-select-choices(repeat="origin in origins | filter: getFilter($select.search)" group-by="'type'" ui-disable-choice="hasZeroBalance(origin)") span(ng-class="{aaa: hasZeroBalance(origin)}") label-origin(origin='origin' highlight="$select.search") - span.help-block(ng-show="sendForm.from.$invalid && sendForm.destinations0.$touched") Must select an account to send from + span.help-block(ng-show="sendForm.from.$invalid && sendForm.destinations0.$touched", translate="MUST_SELECT_ORIGIN") //- Advanced Send .form-group.bc-modal-fade.mvl.advanced-recipient.row //- To label.pts.col-sm-2.col-xs-12 - span(translate="TO:") + span(translate="TO") + |: .col-sm-10.col-xs-12 .flex-column(ng-class="{'advanced-recipient-row': advanced}" ng-repeat="item in transaction.destinations track by $index") p.form-control-static(ng-hide="originsLoaded") diff --git a/app/partials/settings/import-address.jade b/app/partials/settings/import-address.jade index 37f0d471bf..ff6115b995 100644 --- a/app/partials/settings/import-address.jade +++ b/app/partials/settings/import-address.jade @@ -66,7 +66,7 @@ p.form-control-static {{ address.address }} .form-group label.col-sm-3.control-label - span(translate="TO_ACCOUNT") + span(translate="TO") |: .col-sm-7 ui-select(ng-model="fields.account") diff --git a/app/partials/signup.jade b/app/partials/signup.jade index c6050c17c5..4130c04421 100644 --- a/app/partials/signup.jade +++ b/app/partials/signup.jade @@ -1,41 +1,66 @@ div header - h2.em-300(translate="NEW_ACCT_WELCOME", ng-show="currentStep == 1") - form.form-horizontal(ng-show="betaCheckError") - .form-group - p - p - p {{ betaCheckError }} - form.form-horizontal(role="form",name="form",novalidate, ng-show="betaCheckSuccess") - div(ng-switch="currentStep") - div(ng-switch-when="1") - .security-red.mbl.em-400.flex-center - i.ti-hand-stop.mrm.h3.mvn.hidden-xs - span(translate="ALPHA_WARNING") - .form-group(ng-class="{'has-error': errors.email, 'has-success': success.email}") - label.col-sm-4.control-label(translate="EMAIL") - .col-sm-8 - input.form-control(type="email",ng-model="fields.email",autofocus,ng-blur="validate()", ng-focus="errors.email = null") - span.help-block - p {{ errors.email }} - .form-group(ng-class="{'has-error': errors.password, 'has-success': success.password}") - label.col-sm-4.control-label(translate="NEW_PASSWORD") - .col-sm-8 - input.form-control(type="password", name="password",ng-model="fields.password",autofocus,ng-blur="validate()", ng-focus="errors.password = null", ng-maxlength="255", min-entropy="25" required) - password-entropy(password="fields.password").help-block - span.help-block {{ errors.password }} - .form-group(ng-class="{'has-error': errors.confirmation, 'has-success': success.confirmation}") - label.col-sm-4.control-label(translate="CONFIRM_PASSWORD") - .col-sm-8 - input.form-control(type="password",ng-model="fields.confirmation",on-enter="tryNextStep()",autofocus,ng-blur="validate()", ng-focus="errors.confirmation = null") - span.help-block - p {{ errors.confirmation }} - .form-group.flex-center.mtm - .col-sm-4 - input#agreement_accept.pull-right(ng-model="fields.acceptedAgreement" type="checkbox" name="agreement_accept" ng-change="validate()") - label.em-300.col-sm-8 - | I have read and agree to the - a.em-500(ng-click="showAgreement()") Alpha Program Participation Agreement + h2.em-300(translate="NEW_ACCT_WELCOME") + form.form-horizontal( + role="form" + name="signupForm" + ng-submit="signup()" + autocomplete="off" + novalidate) + .security-red.mbl.mtl.em-400.flex-center + i.ti-hand-stop.mrm.h3.mvn.hidden-xs + span(translate="USER_AGREEMENT_WARNING") + .form-group(ng-class="{'has-error': signupForm.email.$invalid && signupForm.email.$touched}") + label.col-sm-4.control-label(translate="EMAIL") + .col-sm-8 + input.form-control( + name="email" + type="email" + ng-model="fields.email" + required + autofocus) + span.help-block(ng-show="signupForm.email.$touched") + p(ng-show="signupForm.email.$error.required" translate="EMAIL_ADDRESS_REQUIRED") + p(ng-show="signupForm.email.$error.email" translate="EMAIL_ADDRESS_INVALID") + .form-group(ng-class="{'has-error': signupForm.password.$invalid && signupForm.password.$touched}") + label.col-sm-4.control-label(translate="NEW_PASSWORD") + .col-sm-8 + input.form-control( + name="password" + type="password" + ng-model="fields.password" + ng-maxlength="255" + min-entropy="25" + required) + password-entropy.help-block(password="fields.password") + span.help-block(ng-show="signupForm.password.$touched") + p(ng-show="signupForm.password.$error.minEntropy && fields.password != undefined" translate="TOO_WEAK") + p(ng-show="signupForm.password.$error.maxlength" translate="TOO_LONG") + .form-group(ng-class="{'has-error': signupForm.confirmation.$invalid && signupForm.confirmation.$touched}") + label.col-sm-4.control-label(translate="CONFIRM_PASSWORD") + .col-sm-8 + input.form-control( + name="confirmation" + type="password" + ng-model="fields.confirmation" + is-valid="fields.confirmation == fields.password" + required) + span.help-block(ng-show="signupForm.confirmation.$touched") + p(ng-show="signupForm.confirmation.$error.isValid" translate="NO_MATCH") + .form-group.flex-center.mtm + .col-sm-4 + input#agreement_accept.pull-right( + name="agreement" + type="checkbox" + ng-model="fields.acceptedAgreement" + required) + label.em-300.col-sm-8 + | I have read and agree to the + a.em-500(ng-click="showAgreement()", translate="USER_AGREEMENT") .flex-center.flex-end.mbl - button.button-primary(ng-click="nextStep()",ng-disabled="!form.$valid || !isValid[0] || !fields.acceptedAgreement", translate="CONTINUE", ng-show="currentStep == 1 && !working") + button.button-primary( + type="submit" + ng-disabled="signupForm.$invalid" + translate="CONTINUE" + ng-show="!working") img(ng-show="working" src="img/spinner.gif") diff --git a/app/partials/transaction.jade b/app/partials/transaction.jade index 3b1b950097..db13e386c8 100644 --- a/app/partials/transaction.jade +++ b/app/partials/transaction.jade @@ -14,7 +14,9 @@ label(translate="FROM:") p.text.mbs {{::from}} .tx-receiver - label(translate="TO:") + label + span(translate="TO") + |: ul.pln.flex-column.flex.type-sm li.flex-center.mbm(ng-repeat="destination in destinations") span.text-overflow-hidden.flex-1.flex.text diff --git a/app/partials/transactions.jade b/app/partials/transactions.jade index 4d93ac7e3e..2ab53f327e 100644 --- a/app/partials/transactions.jade +++ b/app/partials/transactions.jade @@ -8,7 +8,7 @@ translate="{{ f }}" ) .filter-search - input(type="text" placeholder="Search by Account name or Address" ng-model="searchText") + input(type="text" placeholder="{{ 'SEARCH' | translate }}" ng-model="searchText") i.ti-search .transaction-feed .flex-center.flex-justify.flex-column.mtvl(ng-hide="loading || getTotal(accountIndex) > 0 || (transactions | filter:transactionFilter).length > 0 || selectedAccountIndex == 'imported'") diff --git a/app/partials/upgrade.jade b/app/partials/upgrade.jade index af92edba3d..6f460c394f 100644 --- a/app/partials/upgrade.jade +++ b/app/partials/upgrade.jade @@ -1,8 +1,6 @@ .modal-header.flex-center h3(translate="UPGRADE_WALLET") .modal-body.upgrade-modal - uib-alert(type="danger", ng-show="settings.apiAccess") - span(translate="UPGRADE_DISABLES_API_ACCESS") uib-alert(type="danger", close="insist = false", ng-show="insist") span(translate="NEED_SECOND_PASSWORD_FOR_UPGRADE") p(translate="UPGRADE_WELCOME") @@ -13,8 +11,10 @@ li(translate="UPGRADE_PRIVACY") li(translate="UPGRADE_BACKUP") li(translate="UPGRADE_FUND_MANAGEMENT") - p(translate="UPGRADE_LIMITATIONS") - + p + span(translate="UPGRADE_LIMITATIONS") + span + b(translate="UPGRADE_DISABLES_API_ACCESS", ng-show="settings.apiAccess") .modal-footer.pal.flex-end button.button-muted.mrm(ng-click="cancel()", translate="CANCEL", ng-disabled="busy") button.button-success(ui-ladda="busy", ng-click="upgrade()" ng-disabled="waiting", ladda-translate="UPGRADE_NOW", data-style="expand-left") diff --git a/app/partials/terms-of-service.jade b/app/partials/user-agreement.jade similarity index 56% rename from app/partials/terms-of-service.jade rename to app/partials/user-agreement.jade index 7940b25215..f46659c2c2 100644 --- a/app/partials/terms-of-service.jade +++ b/app/partials/user-agreement.jade @@ -1,12 +1,12 @@ .modal-header.bc-modal-header h3 - | ALPHA PROGRAM PARTICIPATION AGREEMENT + | BETA PROGRAM PARTICIPATION AGREEMENT .modal-body .flex-column.pal p - | This Alpha Program Participation Agreement (this “Agreement”) is made and entered into by and between Blockchain Lux S.a.r.l. or its affiliates (“Blockchain”), and you, the “User” who uses the Alpha Services as hereinafter defined. The terms of any other agreement between Blockchain and User are hereby incorporated into the terms of this Agreement, however, in the case of any conflict, the terms of this Agreement supersede and control, except where otherwise specifically stated herein. This Agreement describes the terms and conditions under which User may access and use certain features, technologies and services that are not yet generally commercially available (each, a “Alpha Service”). Blockchain and User are sometimes referred to collectively as the “Parties” and each individually as a “Party.” + | This Beta Program Participation Agreement (this “Agreement”) is made and entered into by and between Blockchain Lux S.a.r.l. or its affiliates (“Blockchain”), and you, the “User” who uses the Beta Services as hereinafter defined. The terms of any other agreement between Blockchain and User are hereby incorporated into the terms of this Agreement, however, in the case of any conflict, the terms of this Agreement supersede and control, except where otherwise specifically stated herein. This Agreement describes the terms and conditions under which User may access and use certain features, technologies and services that are not yet generally commercially available (each, a “Beta Service”). Blockchain and User are sometimes referred to collectively as the “Parties” and each individually as a “Party.” p - b THE SOFTWARE SUPPLIED TO USER IS IN ALPHA FORM AND SUPPLIED FOR TESTING PURPOSES ONLY. BUGS, CRASHES AND OTHER MALFUNCTIONS ARE ALMOST CERTAIN TO OCCUR. UNDER NO CIRCUMSTANCES MAY USER USE THE SOFTWARE SUPPLIED IN CONNECTION WITH THE ALPHA SERVICE TO SECURE MORE THAN $10 WORTH OF BITCOIN. + b THE SOFTWARE SUPPLIED TO USER IS IN BETA FORM AND SUPPLIED FOR TESTING PURPOSES ONLY. BUGS, CRASHES AND OTHER MALFUNCTIONS ARE ALMOST CERTAIN TO OCCUR. UNDER NO CIRCUMSTANCES MAY USER USE THE SOFTWARE SUPPLIED IN CONNECTION WITH THE BETA SERVICE TO SECURE MORE THAN $10 WORTH OF BITCOIN. p b ANY DISPUTES ARISING OUT OF THIS AGREEMENT WILL BE RESOLVED BY BINDING, INDIVIDUAL ARBITRATION AND THE PARTIES WAIVE THEIR RIGHTS TO GO TO COURT OR PARTICIPATE IN A CLASS ACTION LAWSUIT OR CLASS- WIDE ARBITRATION. .bg-light-blue.flex-column.mtl.pal @@ -14,48 +14,48 @@ p ol li - | “Alpha Materials” means any hardware, software, specifications or other technical documentation related to a specific Alpha Service that may be provided to User by Blockchain. + | “Beta Materials” means any hardware, software, specifications or other technical documentation related to a specific Beta Service that may be provided to User by Blockchain. li - | “Alpha Use” means the testing and evaluation of a specific Alpha Service by User and certain other Blockchain customers or business partners. + | “Beta Use” means the testing and evaluation of a specific Beta Service by User and certain other Blockchain customers or business partners. li - | “Alpha Use Information” means all information relating to User’s use, testing or evaluation of a Alpha Service or any related Alpha Materials, including all observations or information regarding the nature, quality, performance, features or functionality of a Alpha Service or any related Alpha Materials. + | “Beta Use Information” means all information relating to User’s use, testing or evaluation of a Beta Service or any related Beta Materials, including all observations or information regarding the nature, quality, performance, features or functionality of a Beta Service or any related Beta Materials. li - | “Feedback” means all feedback, suggestions, and ideas that User provides to Blockchain concerning improvements or enhancements to a Alpha Service or any related Alpha Materials. + | “Feedback” means all feedback, suggestions, and ideas that User provides to Blockchain concerning improvements or enhancements to a Beta Service or any related Beta Materials. li - | “Confidential Information” means all nonpublic information disclosed by Blockchain or its agents to User, its affiliates, or the agents of any of the foregoing, that is designated as confidential or that, given the nature of the information or the circumstances surrounding its disclosure, reasonably should be considered as confidential. Confidential Information includes, without limitation (a) nonpublic information relating to Blockchain’ or its affiliates’ technology, customers, business plans, promotional and marketing activities, finances and other business affairs, (b) third-party information that Blockchain or its affiliates is obligated to keep confidential, (c) Alpha Materials, Alpha Use Information, Feedback, or any other information about or involving (including the existence of) any of the Alpha Uses or Alpha Services, and (d) the nature, content and existence of this Agreement and any discussions or negotiations between the Parties. Confidential Information does not include any information that (i) is or becomes publicly available without breach of this Agreement, (ii) can be shown by documentation to have been known to the receiving party at the time of its receipt from the disclosing party, (iii) is received from a third party who did not acquire or disclose such information by a wrongful or tortious act, or (iv) can be shown by documentation to have been independently developed by the receiving party without reference to any Confidential Information. + | “Confidential Information” means all nonpublic information disclosed by Blockchain or its agents to User, its affiliates, or the agents of any of the foregoing, that is designated as confidential or that, given the nature of the information or the circumstances surrounding its disclosure, reasonably should be considered as confidential. Confidential Information includes, without limitation (a) nonpublic information relating to Blockchain’ or its affiliates’ technology, customers, business plans, promotional and marketing activities, finances and other business affairs, (b) third-party information that Blockchain or its affiliates is obligated to keep confidential, (c) Beta Materials, Beta Use Information, Feedback, or any other information about or involving (including the existence of) any of the Beta Uses or Beta Services, and (d) the nature, content and existence of this Agreement and any discussions or negotiations between the Parties. Confidential Information does not include any information that (i) is or becomes publicly available without breach of this Agreement, (ii) can be shown by documentation to have been known to the receiving party at the time of its receipt from the disclosing party, (iii) is received from a third party who did not acquire or disclose such information by a wrongful or tortious act, or (iv) can be shown by documentation to have been independently developed by the receiving party without reference to any Confidential Information. li - | “Policies” means all policies and guidelines related to any Alpha Service, Alpha Materials or other web services offered by Blockchain or its affiliates and made available to User, including privacy policies, terms of use, acceptable use policies, and any additional terms and conditions for a specific Alpha Use. + | “Policies” means all policies and guidelines related to any Beta Service, Beta Materials or other web services offered by Blockchain or its affiliates and made available to User, including privacy policies, terms of use, acceptable use policies, and any additional terms and conditions for a specific Beta Use. .flex-column.mtl.pal - h2 2. Participation in Alpha Program + h2 2. Participation in Beta Program p ul li - | 2.1. Generally. Blockchain grants User a limited, nonexclusive, non- transferable, royalty- free, revocable license to do the following during the term of the applicable Alpha Use: (a) access and use the Alpha Service solely for internal evaluation purposes; and (b) install, copy, and use any related Alpha Materials solely as necessary to access and use the Alpha Service in the manner permitted by this Agreement. After the conclusion of a Alpha Use, User will not have any further right to use the applicable Alpha Service, and if Blockchain releases a generally-available version of the Alpha Service, User’s use of the generally commercially available version will be subject to separate terms and conditions. However, Blockchain does not guarantee that any Alpha Service will ever be made generally commercially available, or that any generally commercially available version will contain the same or similar functionality as the version made available by Blockchain during the Alpha Use. + | 2.1. Generally. Blockchain grants User a limited, nonexclusive, non- transferable, royalty- free, revocable license to do the following during the term of the applicable Beta Use: (a) access and use the Beta Service solely for internal evaluation purposes; and (b) install, copy, and use any related Beta Materials solely as necessary to access and use the Beta Service in the manner permitted by this Agreement. After the conclusion of a Beta Use, User will not have any further right to use the applicable Beta Service, and if Blockchain releases a generally-available version of the Beta Service, User’s use of the generally commercially available version will be subject to separate terms and conditions. However, Blockchain does not guarantee that any Beta Service will ever be made generally commercially available, or that any generally commercially available version will contain the same or similar functionality as the version made available by Blockchain during the Beta Use. li | 2.2. Blockchain reserves the right to amend this agreement at its discretion and for any reason by giving 30 days’ notice to User, after which time the amendment will become effective to bind the parties. li | 2.3. Restrictions and Limitations. User will not: ul li - | i. allow access to any Alpha Service or Alpha Materials by any third party other than User’s employees and contractors who (i) have a need to use or access the Alpha Service or Alpha Materials in connection with User’s internal evaluation activities and (ii) have executed written nondisclosure agreements obligating them to protect the confidentiality of the Alpha Service and Alpha Materials; + | i. allow access to any Beta Service or Beta Materials by any third party other than User’s employees and contractors who (i) have a need to use or access the Beta Service or Beta Materials in connection with User’s internal evaluation activities and (ii) have executed written nondisclosure agreements obligating them to protect the confidentiality of the Beta Service and Beta Materials; li - | ii. violate any usage limits for a Alpha Service that Blockchain may communicate to User; + | ii. violate any usage limits for a Beta Service that Blockchain may communicate to User; li - | iii. export or allow access to any Alpha Service or Alpha Materials in any manner contrary to the export regulations of the United States; or + | iii. export or allow access to any Beta Service or Beta Materials in any manner contrary to the export regulations of the United States; or li - | iv. otherwise access or use any Alpha Service, or install, copy or use any Alpha Materials, in any manner or for any purpose not expressly permitted by this Agreement. + | iv. otherwise access or use any Beta Service, or install, copy or use any Beta Materials, in any manner or for any purpose not expressly permitted by this Agreement. - | Blockchain may modify the permitted use of or suspend User’s access to any Alpha Service at any time and for any reason. Alpha Services also may be unavailable or their performance may be negatively affected by scheduled maintenance. No service levels or other uptime guarantees apply to the Alpha Services. Blockchain may or may not, at its discretion, notify User in advance of scheduled maintenance, but Blockchain is unable to provide advance notice of unscheduled or emergency maintenance. + | Blockchain may modify the permitted use of or suspend User’s access to any Beta Service at any time and for any reason. Beta Services also may be unavailable or their performance may be negatively affected by scheduled maintenance. No service levels or other uptime guarantees apply to the Beta Services. Blockchain may or may not, at its discretion, notify User in advance of scheduled maintenance, but Blockchain is unable to provide advance notice of unscheduled or emergency maintenance. li - | 2.4. Alpha Use Information and Feedback. Alpha Use Information and Feedback. In consideration of the rights granted in this Agreement, User will provide Alpha Use Information, when and in the form reasonably requested by Blockchain. Blockchain will have a perpetual and irrevocable right to use, evaluate and otherwise exploit all Alpha Use Information for its own purposes. User will not use any Alpha Use Information except for its internal evaluation purposes. Blockchain has a perpetual and irrevocable right to use and exploit all Feedback and may use the Feedback without accounting or compensation to User. User will not provide any Alpha Use Information or Feedback unless it has all rights necessary to do so. + | 2.4. Beta Use Information and Feedback. Beta Use Information and Feedback. In consideration of the rights granted in this Agreement, User will provide Beta Use Information, when and in the form reasonably requested by Blockchain. Blockchain will have a perpetual and irrevocable right to use, evaluate and otherwise exploit all Beta Use Information for its own purposes. User will not use any Beta Use Information except for its internal evaluation purposes. Blockchain has a perpetual and irrevocable right to use and exploit all Feedback and may use the Feedback without accounting or compensation to User. User will not provide any Beta Use Information or Feedback unless it has all rights necessary to do so. .bg-light-blue.flex-column.mtl.pal h2 3. Term and Termination p ul li - | 3.1. Term. Blockchain may, at its discretion, specify the term of each individual Alpha Use, but the term will automatically terminate upon the release of a generally commercially available version of the applicable Alpha Service. The term of this Agreement will commence on the date you begin to use the Alpha Service and will continue until terminated pursuant to Section 3.2. + | 3.1. Term. Blockchain may, at its discretion, specify the term of each individual Beta Use, but the term will automatically terminate upon the release of a generally commercially available version of the applicable Beta Service. The term of this Agreement will commence on the date you begin to use the Beta Service and will continue until terminated pursuant to Section 3.2. li - | 3.2. Termination. Either Party may terminate User’s participation in an individual Alpha Use, or this Agreement entirely, at any time for any reason upon notice to the other Party. Upon termination of this Agreement: (a) all rights and licenses granted to User in this Agreement will immediately terminate; (b) User will immediately return or, if instructed by Blockchain, destroy all Alpha Materials or any other confidential or proprietary information of Blockchain or its affiliates related to any Alpha Service or this Agreement; and (c) Sections 2.4 and 4 through 8 will survive. + | 3.2. Termination. Either Party may terminate User’s participation in an individual Beta Use, or this Agreement entirely, at any time for any reason upon notice to the other Party. Upon termination of this Agreement: (a) all rights and licenses granted to User in this Agreement will immediately terminate; (b) User will immediately return or, if instructed by Blockchain, destroy all Beta Materials or any other confidential or proprietary information of Blockchain or its affiliates related to any Beta Service or this Agreement; and (c) Sections 2.4 and 4 through 8 will survive. .flex-column.mtl.pal h2 4. Confidentiality p @@ -63,15 +63,15 @@ li | 4.1. Use and Disclosure. User may not disclose any Confidential Information during the term of this Agreement or at any time during the three (3) year period following the end of the Term. If the Parties have executed a separate non-disclosure agreement (the “NDA”) and there is a conflict between the terms of the NDA and the terms of this Section 4.1, the terms of the NDA will control. li - | 4.2. Publicity. Neither Party will issue any press release or public statement regarding this Agreement or any Alpha Use, Alpha Service or Alpha Materials unless Blockchain has approved in writing the time, form and content of the information to be disseminated to third parties or the public. + | 4.2. Publicity. Neither Party will issue any press release or public statement regarding this Agreement or any Beta Use, Beta Service or Beta Materials unless Blockchain has approved in writing the time, form and content of the information to be disseminated to third parties or the public. .bg-light-blue.flex-column.mtl.pal h2 5. DISCLAIMER OF WARRANTIES p ul li - | 5.1. THE ALPHA SERVICES AND ALPHA MATERIALS ARE NOT READY FOR GENERAL COMMERCIAL RELEASE AND MAY CONTAIN BUGS, ERRORS, DEFECTS OR HARMFUL COMPONENTS. ACCORDINGLY, BLOCKCHAIN IS PROVIDING THE ALPHA SERVICES AND ALPHA MATERIALS TO USER + | 5.1. THE BETA SERVICES AND BETA MATERIALS ARE NOT READY FOR GENERAL COMMERCIAL RELEASE AND MAY CONTAIN BUGS, ERRORS, DEFECTS OR HARMFUL COMPONENTS. ACCORDINGLY, BLOCKCHAIN IS PROVIDING THE BETA SERVICES AND BETA MATERIALS TO USER b “AS IS.” - | BLOCKCHAIN MAKES NO WARRANTIES OF ANY KIND WITH RESPECT TO THE ALPHA SERVICES OR ALPHA MATERIALS, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. NOTWITHSTANDING ANY PUBLISHED MATERIALS THAT STATE OTHERWISE, BLOCKCHAIN DOES NOT WARRANT THAT THE ALPHA SERVICES OR ALPHA MATERIALS WILL BE ERROR-FREE + | BLOCKCHAIN MAKES NO WARRANTIES OF ANY KIND WITH RESPECT TO THE BETA SERVICES OR BETA MATERIALS, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. NOTWITHSTANDING ANY PUBLISHED MATERIALS THAT STATE OTHERWISE, BLOCKCHAIN DOES NOT WARRANT THAT THE BETA SERVICES OR BETA MATERIALS WILL BE ERROR-FREE | OR THAT THEY WILL MEET ANY SPECIFIED SERVICE LEVEL, OR WILL OPERATE WITHOUT INTERRUPTIONS OR DOWNTIME. .flex-column.mtl.pal h2 6. LIMITATION OF LIABILITY @@ -84,7 +84,7 @@ p ul li - | 7.1. For any and all controversies, disputes, demands, claims, or causes of action between the parties (including the interpretation and scope of this Section and the arbitrability of the controversy, dispute, demand, claim, or cause of action) relating to the Alpha Service, Alpha Use or this Agreement (as well as any related or prior agreement), the Parties agree to resolve any such controversy, dispute, demand, claim, or cause of action exclusively through binding and confidential arbitration. The arbitration will take place in the federal judicial district of the User’s residence if the User resides in the United States, or in New York City if the User does not. As used in this Section, “we” and “us” mean Blockchain. In addition, “we” and “us” include any third party providing any product, service, or benefit in connection with the Alpha Services or this Agreement (as well as any related or prior agreement that you may have had with us) if such third party is named as a co-party with us in any controversy, dispute, demand, claim, or cause of action subject to this Section. + | 7.1. For any and all controversies, disputes, demands, claims, or causes of action between the parties (including the interpretation and scope of this Section and the arbitrability of the controversy, dispute, demand, claim, or cause of action) relating to the Beta Service, Beta Use or this Agreement (as well as any related or prior agreement), the Parties agree to resolve any such controversy, dispute, demand, claim, or cause of action exclusively through binding and confidential arbitration. The arbitration will take place in the federal judicial district of the User’s residence if the User resides in the United States, or in New York City if the User does not. As used in this Section, “we” and “us” mean Blockchain. In addition, “we” and “us” include any third party providing any product, service, or benefit in connection with the Beta Services or this Agreement (as well as any related or prior agreement that you may have had with us) if such third party is named as a co-party with us in any controversy, dispute, demand, claim, or cause of action subject to this Section. li | 7.2. Arbitration will be subject to the Federal Arbitration Act and not any state arbitration law. The arbitration will be conducted before one commercial arbitrator from the American Arbitration Association (“AAA”) with substantial experience in resolving commercial contract disputes. As modified by this Agreement, and unless otherwise agreed upon by the parties in writing, the arbitration will be governed by the AAA’s Commercial Arbitration Rules and, if the arbitrator deems them applicable, the Supplementary Procedures for Consumer Related Disputes (collectively, the “Rules and Procedures”). Where no claims or counterclaims exceed $10,000, the dispute will be resolved by the submission of documents without a hearing, unless a hearing is | requested by a party or deemed necessary by the arbitrator, in which case, a party may elect to participate telephonically. @@ -106,7 +106,7 @@ p ul li - | 8.1. Except for those limited rights expressly granted in Section 2.1, Blockchain and its licensors retain all right, title and interest in and to the Alpha Services and the Alpha Materials, including all related intellectual property rights. The parties are independent contractors with respect to each other, and nothing in this Agreement shall be construed as creating an employer-employee relationship, a partnership, agency relationship or a joint venture between the parties. This Agreement further controls the actions of all party representatives, officers, agents, employees and associated individuals. The terms of this Agreement shall be binding on the parties, and all successors to the foregoing who take their rights hereunder. Neither party will assign, transfer or delegate its rights or obligations under this Agreement (in whole or in part) without the other party’s prior + | 8.1. Except for those limited rights expressly granted in Section 2.1, Blockchain and its licensors retain all right, title and interest in and to the Beta Services and the Beta Materials, including all related intellectual property rights. The parties are independent contractors with respect to each other, and nothing in this Agreement shall be construed as creating an employer-employee relationship, a partnership, agency relationship or a joint venture between the parties. This Agreement further controls the actions of all party representatives, officers, agents, employees and associated individuals. The terms of this Agreement shall be binding on the parties, and all successors to the foregoing who take their rights hereunder. Neither party will assign, transfer or delegate its rights or obligations under this Agreement (in whole or in part) without the other party’s prior | written consent except that Blockchain may assign and delegate this Agreement pursuant to a transfer of all or substantially all of Blockchain’s business and assets, whether by merger, sale of assets, sale of stock, or otherwise. Any attempted assignment, transfer or delegation in violation of the foregoing shall be null and void. All modifications to and waivers of any terms of this Agreement must be in a writing that is signed by the parties hereto and expressly references this Agreement. This Agreement shall be governed by the laws of the State of New York, without regard to New York conflict of laws rules. The exclusive venue and jurisdiction for any and all disputes, claims and controversies arising from or relating to this Agreement shall be the state or federal courts located in New York City. In the event that any provision of this Agreement conflicts with governing law or if any provision is held to be null, void or otherwise ineffective or invalid by a court of competent jurisdiction, (a) such provision shall be deemed to be restated to reflect as nearly as possible the original intentions of the parties in accordance with applicable law, and (b) the remaining terms, provisions, covenants and restrictions of this Agreement shall remain in full force and effect. No waiver of any breach of any provision of this Agreement shall constitute a waiver of any prior, concurrent or subsequent breach of the same or any other provisions hereof, and no waiver shall be effective unless made in writing and signed by an authorized representative of the waiving party. This Agreement and all expressly referenced documents constitute the entire agreement between the parties with respect to the subject matter hereof and supersedes all prior and contemporaneous agreements or communications, including any reseller or similar agreements previously executed by the parties. All notices, consents and approvals under this Agreement must be delivered in writing by email to the email address supplied by one party to the other during the account signup process, or posted to the Blockchain website. .modal-footer.flex-end.pal - button.button-success.button-lg(ng-click="$dismiss()", translate="OK") + button.button-success.button-lg(ng-click="$close()" translate="OK") diff --git a/app/templates/destination-input.jade b/app/templates/destination-input.jade index a59684eea0..8328fc2e9e 100644 --- a/app/templates/destination-input.jade +++ b/app/templates/destination-input.jade @@ -10,7 +10,7 @@ type="text" tabindex="1" autocomplete="off" - placeholder="Paste or scan an address or select an account" + placeholder="Paste or scan an address or select a destination" ng-model="model.address" ng-hide="model.type === 'Accounts'" ng-blur="blur()" @@ -29,8 +29,6 @@ span.caret span.sr-only Toggle Dropdown ul.uib-dropdown-menu.dropdown-menu-right.drop-menu - li.dropdown-header - | Accounts li(ng-repeat="account in accounts") a(ng-click="setModel(account)" ng-disabled="true") | {{ account.label }} diff --git a/assets/js/controllers/accountForm.controller.js b/assets/js/controllers/accountForm.controller.js index 1fea89249c..9c6e7881ff 100644 --- a/assets/js/controllers/accountForm.controller.js +++ b/assets/js/controllers/accountForm.controller.js @@ -29,14 +29,6 @@ function AccountFormCtrl($scope, Wallet, $uibModalInstance, $log, $translate, ac $scope.status.busy = false; $uibModalInstance.dismiss(""); Wallet.saveActivity(3); - $translate(['SUCCESS', 'ACCOUNT_CREATED']).then(translations => { - $scope.$emit('showNotification', { - type: 'created-account', - icon: 'ti-layout-list-post', - heading: translations.SUCCESS, - msg: translations.ACCOUNT_CREATED - }); - }); }; const error = () => {$scope.status.busy = false;} diff --git a/assets/js/controllers/app.controller.js b/assets/js/controllers/app.controller.js index 0b96959b20..b662744b3d 100644 --- a/assets/js/controllers/app.controller.js +++ b/assets/js/controllers/app.controller.js @@ -2,10 +2,11 @@ angular .module('walletApp') .controller("AppCtrl", AppCtrl); -function AppCtrl($scope, Wallet, Alerts, $state, $rootScope, $location, $cookieStore, $timeout, $uibModal, $window, $translate) { +function AppCtrl($scope, Wallet, Alerts, $state, $rootScope, $cookies, $location, $timeout, $uibModal, $window, $translate) { $scope.status = Wallet.status; $scope.settings = Wallet.settings; $rootScope.isMock = Wallet.isMock; + $rootScope.loginFormUID = $cookies.get("uid"); // Last entered in login form $scope.goal = Wallet.goal; $scope.menu = { isCollapsed: false @@ -59,9 +60,9 @@ function AppCtrl($scope, Wallet, Alerts, $state, $rootScope, $location, $cookieS }; $scope.$on('$stateChangeSuccess', (event, toState, toParams, fromState, fromParams) => { - let loggedOutStates = ['public', 'public.login', 'public.recover', 'public.reminder', 'public.signup', 'public.help', 'open']; + let loggedOutStates = ['public', 'public.login-no-uid', 'public.login-uid', 'public.reset-two-factor', 'public.recover', 'public.reminder', 'public.signup', 'public.help', 'open', 'wallet.common.verify-email', 'wallet.common.unsubscribe', 'public.authorize-approve', 'public.reset-two-factor-token']; if (loggedOutStates.every(s => toState.name !== s) && $scope.status.isLoggedIn === false) { - $state.go("public.login"); + $state.go("public.login-no-uid"); } if (Wallet.status.isLoggedIn && (Wallet.store.resetLogoutTimeout != null)) { Wallet.store.resetLogoutTimeout(); diff --git a/assets/js/controllers/authorizeApprove.controller.js b/assets/js/controllers/authorizeApprove.controller.js new file mode 100644 index 0000000000..fd911875be --- /dev/null +++ b/assets/js/controllers/authorizeApprove.controller.js @@ -0,0 +1,75 @@ +angular + .module('walletApp') + .controller("AuthorizeApproveCtrl", AuthorizeApproveCtrl); + +function AuthorizeApproveCtrl($window, $scope, Wallet, $stateParams, $state, Alerts, $translate) { + const success = (uid) => { + $scope.checkingToken = false; + $scope.busyApproving = false; + $scope.busyRejecting = false; + + + $window.close(); // This is sometimes ignored, hence the code below: + + if(uid) { + $translate('AUTHORIZE_APPROVE_SUCCESS').then(translation => { + $state.go("public.login-uid", {uid: uid}).then(() => { + Alerts.displaySuccess(translation) + }); + }); + } else { + $translate('AUTHORIZE_APPROVE_SUCCESS').then(translation => { + $state.go("public.login-no-uid").then(() => { + Alerts.displaySuccess(translation) + }); + }); + } + } + + const error = (message) => { + $scope.checkingToken = false; + $scope.busyApproving = false; + $scope.busyRejecting = false; + + $state.go("public.login-no-uid"); + Alerts.displayError(message, true); + } + + const differentBrowser = (details) => { + $scope.checkingToken = false; + + $scope.differentBrowser = true; + $scope.details = details; + } + + $scope.checkingToken = true; + + Wallet.authorizeApprove($stateParams.token, differentBrowser, null) + .then(success) + .catch(error); + + $scope.approve = () => { + $scope.busyApproving = true; + Wallet.authorizeApprove($stateParams.token, () => {}, true) + .then(success) + .catch(error); + } + + $scope.reject = () => { + $scope.busyRejecting = true; + + const rejected = () => { + $scope.busyRejecting = false; + + $translate('AUTHORIZE_REJECT_SUCCESS').then(translation => { + $state.go("public.login-no-uid").then(() => { + Alerts.displaySuccess(translation) + }); + }); + }; + + Wallet.authorizeApprove($stateParams.token, () => {}, false) + .then(rejected) + .catch(error); + } +} diff --git a/assets/js/controllers/claim.controller.js b/assets/js/controllers/claim.controller.js index 40947acad6..d80861f465 100644 --- a/assets/js/controllers/claim.controller.js +++ b/assets/js/controllers/claim.controller.js @@ -7,6 +7,6 @@ function ClaimCtrl($scope, Wallet, $translate, $stateParams, $state, Alerts) { Wallet.goal.claim = {code: $stateParams.code, balance: balance}; if (!Wallet.status.isLoggedIn) { Alerts.displayInfo("Please login to your wallet or create a new one to proceed.", true); - $state.go("public.login"); + $state.go("public.login-no-uid"); } } diff --git a/assets/js/controllers/login.controller.js b/assets/js/controllers/login.controller.js index fb1826a682..2fcefa26be 100644 --- a/assets/js/controllers/login.controller.js +++ b/assets/js/controllers/login.controller.js @@ -2,18 +2,20 @@ angular .module('walletApp') .controller("LoginCtrl", LoginCtrl); -function LoginCtrl($scope, $rootScope, $log, $http, Wallet, Alerts, $cookieStore, $uibModal, $state, $timeout, $translate, filterFilter) { +function LoginCtrl($scope, $rootScope, $log, $http, Wallet, Alerts, $cookies, $uibModal, $state, $stateParams, $timeout, $translate, filterFilter) { $scope.status = Wallet.status; $scope.settings = Wallet.settings; $scope.disableLogin = null; - $scope.status.enterkey = false; - $scope.key = $cookieStore.get("key"); $scope.errors = { uid: null, password: null, twoFactor: null }; - $scope.uidAvailable = $cookieStore.get('uid') != null; + + $scope.uid = $stateParams.uid || Wallet.guid || $rootScope.loginFormUID; + + $scope.uidAvailable = !!$scope.uid + $scope.user = Wallet.user; // Browser compatibility warnings: @@ -85,30 +87,11 @@ function LoginCtrl($scope, $rootScope, $log, $http, Wallet, Alerts, $cookieStore Alerts.displayWarning(translation, true); }); } - if (Wallet.guid != null) { - $scope.uid = Wallet.guid; - } else { - $scope.uid = $cookieStore.get("uid"); - } - if ($scope.key != null) { - $scope.status.enterkey = true; - } - if ($cookieStore.get('email-verified')) { - $cookieStore.remove('email-verified'); - $translate(['SUCCESS', 'EMAIL_VERIFIED_SUCCESS', 'EMAIL_VERIFIED_SUCCESS_NO_UID']).then(translations => { - $scope.$emit('showNotification', { - type: 'verified-email', - icon: 'ti-email', - heading: translations.SUCCESS, - msg: $scope.uidAvailable ? translations.EMAIL_VERIFIED_SUCCESS : translations.EMAIL_VERIFIED_SUCCESS_NO_UID - }); - }); - } $scope.twoFactorCode = ""; $scope.busy = false; $scope.isValid = false; - if (!!$cookieStore.get("password")) { - $scope.password = $cookieStore.get("password"); + if (!!$cookies.get("password")) { + $scope.password = $cookies.get("password"); } $scope.login = () => { if ($scope.busy) return; @@ -137,10 +120,10 @@ function LoginCtrl($scope, $rootScope, $log, $http, Wallet, Alerts, $cookieStore Wallet.login($scope.uid, $scope.password, null, needs2FA, success, error); } if ($scope.uid != null && $scope.uid !== "") { - $cookieStore.put("uid", $scope.uid); + $cookies.put("uid", $scope.uid); } if ($scope.savePassword && $scope.password != null && $scope.password !== "") { - $cookieStore.put("password", $scope.password); + $cookies.put("password", $scope.password); } }; @@ -174,6 +157,7 @@ function LoginCtrl($scope, $rootScope, $log, $http, Wallet, Alerts, $cookieStore }); $scope.$watch("uid + password + twoFactor", () => { + $rootScope.loginFormUID = $scope.uid; let isValid = null; $scope.errors.uid = null; $scope.errors.password = null; diff --git a/assets/js/controllers/lostGuid.controller.js b/assets/js/controllers/lostGuid.controller.js index b52fbbd5f1..57856a21cb 100644 --- a/assets/js/controllers/lostGuid.controller.js +++ b/assets/js/controllers/lostGuid.controller.js @@ -2,7 +2,7 @@ angular .module('walletApp') .controller('LostGuidCtrl', LostGuidCtrl); -function LostGuidCtrl($scope, $http, $translate, Wallet, Alerts) { +function LostGuidCtrl($scope, $rootScope, $http, $translate, Wallet, Alerts) { $scope.currentStep = 1; $scope.fields = { email: '', @@ -11,7 +11,7 @@ function LostGuidCtrl($scope, $http, $translate, Wallet, Alerts) { $scope.refreshCaptcha = () => { let time = new Date().getTime(); - $scope.captchaSrc = `https://blockchain.info/kaptcha.jpg?timestamp=${time}`; + $scope.captchaSrc = $rootScope.rootURL + `kaptcha.jpg?timestamp=${time}`; $scope.fields.captcha = ''; }; @@ -24,33 +24,17 @@ function LostGuidCtrl($scope, $http, $translate, Wallet, Alerts) { let error = (res) => { $scope.working = false; $scope.refreshCaptcha(); - switch (res.data.initial_error) { - case 'Captcha Code Incorrect': - Alerts.displayError($translate.instant('CAPTCHA_INCORRECT')); - break; - case 'Quota Exceeded': - Alerts.displayError($translate.instant('QUOTA_EXCEEDED')); - break; - default: - Alerts.displayError($translate.instant('UNKNOWN_ERROR')); - } }; - let httpOptions = { - url : 'https://blockchain.info/wallet/recover-wallet', - method : 'GET', - params : { - param1 : $scope.fields.email, - kaptcha : $scope.fields.captcha, - format : 'json' - }, - withCredentials: true - }; - $http(httpOptions).then(success).catch(error); + + $scope.remindForm.$setPristine(); + $scope.remindForm.$setUntouched(); + + Wallet.recoverGuid($scope.fields.email, $scope.fields.captcha).then(success).catch(error); }; // Set SID cookie by requesting headers $http({ - url: 'https://blockchain.info/wallet/login', + url: $rootScope.rootURL + 'wallet/login', method: 'HEAD', withCredentials: true }).then($scope.refreshCaptcha); diff --git a/assets/js/controllers/navigation.controller.js b/assets/js/controllers/navigation.controller.js index bf238b38a1..74bc17e086 100644 --- a/assets/js/controllers/navigation.controller.js +++ b/assets/js/controllers/navigation.controller.js @@ -2,7 +2,7 @@ angular .module('walletApp') .controller("NavigationCtrl", NavigationCtrl); -function NavigationCtrl($scope, Wallet, currency, SecurityCenter, $translate, $cookieStore, $state, filterFilter, $interval) { +function NavigationCtrl($scope, Wallet, currency, SecurityCenter, $translate, $cookies, $state, filterFilter, $interval) { $scope.status = Wallet.status; $scope.security = SecurityCenter.security; $scope.settings = Wallet.settings; @@ -36,8 +36,8 @@ function NavigationCtrl($scope, Wallet, currency, SecurityCenter, $translate, $c if (confirm(translation)) { $scope.uid = null; $scope.password = null; - $cookieStore.remove("password"); -// $cookieStore.remove("uid") // Pending a "Forget Me feature" + $cookies.remove("password"); +// $cookies.remove("uid") // Pending a "Forget Me feature" $state.go("wallet.common.transactions", { accountIndex: "" diff --git a/assets/js/controllers/openLink.controller.js b/assets/js/controllers/openLink.controller.js index 5cbd69bc28..59417eb399 100644 --- a/assets/js/controllers/openLink.controller.js +++ b/assets/js/controllers/openLink.controller.js @@ -9,6 +9,6 @@ function OpenLinkController($scope, Wallet, $translate, $stateParams, $state, Al $translate("PLEASE_LOGIN_FIRST").then(translation => { Alerts.displayInfo(translation, true); }); - $state.go("public.login"); + $state.go("public.login-no-uid"); } } diff --git a/assets/js/controllers/recoverFunds.controller.js b/assets/js/controllers/recoverFunds.controller.js index 12cc2480ee..60b3d63640 100644 --- a/assets/js/controllers/recoverFunds.controller.js +++ b/assets/js/controllers/recoverFunds.controller.js @@ -17,7 +17,6 @@ function RecoverFundsCtrl($scope, $rootScope, $state, $timeout, $translate, Wall $scope.working = true; const success = (wallet) => { - $rootScope.beta = false; $scope.working = false; $scope.nextStep(); $rootScope.$safeApply(); @@ -29,7 +28,7 @@ function RecoverFundsCtrl($scope, $rootScope, $state, $timeout, $translate, Wall console.error(err); }; $timeout(() => { - $state.go('public.login'); + $state.go('public.login-uid', {uid: wallet.guid}); Wallet.login( wallet.guid, wallet.password, null, null, loginSuccess, loginError ); diff --git a/assets/js/controllers/request.controller.js b/assets/js/controllers/request.controller.js index da25d460ec..a88fe3f485 100644 --- a/assets/js/controllers/request.controller.js +++ b/assets/js/controllers/request.controller.js @@ -22,7 +22,7 @@ function RequestCtrl($scope, Wallet, Alerts, currency, $uibModalInstance, $log, for(const account of $scope.accounts()) { if (account.index != null && !account.archived) { let acct = angular.copy(account); - acct.type = "Accounts"; + acct.type = ""; $scope.destinations.push(acct); if ((destination != null) && (destination.index != null) && destination.index === acct.index) { $scope.fields.to = acct; diff --git a/assets/js/controllers/resetTwoFactor.controller.js b/assets/js/controllers/resetTwoFactor.controller.js new file mode 100644 index 0000000000..896d4675f0 --- /dev/null +++ b/assets/js/controllers/resetTwoFactor.controller.js @@ -0,0 +1,53 @@ +angular + .module('walletApp') + .controller('ResetTwoFactorCtrl', ResetTwoFactorCtrl); + +function ResetTwoFactorCtrl($scope, $rootScope, $http, $translate, Wallet, Alerts) { + + $scope.currentStep = 1; + $scope.fields = { + uid: $rootScope.loginFormUID, + email: '', + newEmail: '', + secret: '', + message: '', + captcha: '' + }; + + $scope.refreshCaptcha = () => { + let time = new Date().getTime(); + $scope.captchaSrc = $rootScope.rootURL + `kaptcha.jpg?timestamp=${time}`; + $scope.fields.captcha = ''; + }; + + $scope.resetTwoFactor = () => { + $scope.working = true; + let success = (res) => { + $scope.working = false; + $scope.currentStep = 2; + }; + let error = (e) => { + $scope.working = false; + $scope.refreshCaptcha(); + }; + + $scope.form.$setPristine(); + $scope.form.$setUntouched(); + + Wallet.requestTwoFactorReset( + $scope.fields.uid, + $scope.fields.email, + $scope.fields.newEmail, + $scope.fields.secret, + $scope.fields.message, + $scope.fields.captcha, + ).then(success).catch(error); + }; + + // Set SID cookie by requesting headers + $http({ + url: $rootScope.rootURL + 'wallet/login', + method: 'HEAD', + withCredentials: true + }).then($scope.refreshCaptcha); +} diff --git a/assets/js/controllers/resetTwoFactorToken.controller.js b/assets/js/controllers/resetTwoFactorToken.controller.js new file mode 100644 index 0000000000..218de4e04c --- /dev/null +++ b/assets/js/controllers/resetTwoFactorToken.controller.js @@ -0,0 +1,31 @@ +angular + .module('walletApp') + .controller("ResetTwoFactorTokenCtrl", ResetTwoFactorTokenCtrl); + +function ResetTwoFactorTokenCtrl($scope, Wallet, $stateParams, $state, Alerts, $translate, $rootScope) { + const success = (obj) => { + + $scope.checkingToken = false + + $translate(['SUCCESS']).then(translations => { + $state.go("public.login-uid", {uid: obj.guid}).then(() =>{ + $rootScope.$emit('showNotification', { + type: 'verified-email', + icon: 'ti-email', + heading: translations.SUCCESS, + msg: obj.message + }); + }); + }); + } + + const error = (message) => { + $scope.checkingToken = false + $state.go("public.login-no-uid"); + Alerts.displayError(message, true); + } + + $scope.checkingToken = true + + Wallet.resetTwoFactorToken($stateParams.token).then(success).catch(error); +} diff --git a/assets/js/controllers/send.controller.js b/assets/js/controllers/send.controller.js index 28f33c30c1..19462dec1d 100644 --- a/assets/js/controllers/send.controller.js +++ b/assets/js/controllers/send.controller.js @@ -228,7 +228,7 @@ function SendCtrl($scope, $log, Wallet, Alerts, currency, $uibModalInstance, $ti } else { let dest = destinations[0]; $scope.toLabel = dest.index == null ? - dest.label || dest.address : `${dest.label} Account`; + dest.label || dest.address : `${dest.label}`; } }; @@ -271,7 +271,7 @@ function SendCtrl($scope, $log, Wallet, Alerts, currency, $uibModalInstance, $ti balance: origin.balance, archived: origin.archived }; - formatted.type = origin.index != null ? 'Accounts' : 'Imported Addresses'; + formatted.type = origin.index != null ? '' : 'Imported Addresses'; if (origin.index == null) formatted.isWatchOnly = origin.isWatchOnly; return formatted; }; diff --git a/assets/js/controllers/settings/settings.controller.js b/assets/js/controllers/settings/settings.controller.js index 24d63437d8..8ac8feb871 100644 --- a/assets/js/controllers/settings/settings.controller.js +++ b/assets/js/controllers/settings/settings.controller.js @@ -2,7 +2,7 @@ angular .module('walletApp') .controller("SettingsCtrl", SettingsCtrl); -function SettingsCtrl($scope, Wallet, Alerts, $cookieStore, $state) { +function SettingsCtrl($scope, Wallet, Alerts, $state) { if ($state.current.name === "wallet.common.settings") { $state.go("wallet.common.settings.info"); } diff --git a/assets/js/controllers/signup.controller.js b/assets/js/controllers/signup.controller.js index f9a7a7a434..16778370b4 100644 --- a/assets/js/controllers/signup.controller.js +++ b/assets/js/controllers/signup.controller.js @@ -2,11 +2,10 @@ angular .module('walletApp') .controller("SignupCtrl", SignupCtrl); -function SignupCtrl($scope, $rootScope, $log, Wallet, Alerts, currency, $uibModal, $translate, $cookieStore, $filter, $state, $http, languages) { - $scope.currentStep = 1; +SignupCtrl.$inject = ['$scope', '$state', '$cookies', '$filter', '$translate', '$uibModal', 'Wallet', 'Alerts', 'currency', 'languages']; + +function SignupCtrl($scope, $state, $cookies, $filter, $translate, $uibModal, Wallet, Alerts, currency, languages) { $scope.working = false; - $scope.languages = languages; - $scope.currencies = currency.currencies; $scope.alerts = Alerts.alerts; $scope.status = Wallet.status; @@ -17,49 +16,26 @@ function SignupCtrl($scope, $rootScope, $log, Wallet, Alerts, currency, $uibModa } }); - $scope.isValid = [true, true]; let language_guess = $filter("getByProperty")("code", $translate.use(), languages); if (language_guess == null) { - language_guess = $filter("getByProperty")("code", "en", languages); + $scope.language_guess = $filter("getByProperty")("code", "en", languages); } - const currency_guess = $filter("getByProperty")("code", "USD", currency.currencies); + $scope.currency_guess = $filter("getByProperty")("code", "USD", currency.currencies); + $scope.fields = { email: "", password: "", confirmation: "", - language: language_guess, - currency: currency_guess, acceptedAgreement: false }; - $scope.betaCheckSuccess = false; - - - // If BETA=1 is set in .env then in index.html/jade $rootScope.beta is set. - // The following checks are not ideal as they can be bypassed with some creative Javascript commands. - if ($rootScope.beta) { - // Check if we allow signups at the moment: - $http.post("/check_beta", {}).success((data) => { - if (data.open) { - $scope.betaCheckSuccess = true; - } else { - if (data.error && data.error.message) { - $scope.betaCheckError = data.error.message; - } - } - }).error(() => { - $scope.betaCheckError = "There is a problem with our alpha wallet access control system, please try again later."; - }); - } else { - $scope.betaCheckSuccess = true; - } - $scope.showAgreement = () => { const modalInstance = $uibModal.open({ - templateUrl: "partials/alpha-agreement.jade", - controller: 'SignupCtrl', + templateUrl: "partials/user-agreement.jade", + controller: function () {}, windowClass: "bc-modal terms-modal" }); + modalInstance.result.then(() => $scope.fields.acceptedAgreement = true); }; $scope.close = () => { @@ -67,120 +43,36 @@ function SignupCtrl($scope, $rootScope, $log, Wallet, Alerts, currency, $uibModa $state.go("wallet.common.home"); }; - $scope.tryNextStep = () => { - if ($scope.isValid[0]) $scope.nextStep(); - }; - - $scope.nextStep = () => { - $scope.validate(); - if ($scope.isValid[$scope.currentStep - 1]) { - if ($scope.currentStep === 1) { - $scope.working = true; - $scope.createWallet( uid => { - $scope.working = false; - if (uid != null) { - $cookieStore.put("uid", uid); - } - if ($scope.savePassword) { - $cookieStore.put("password", $scope.fields.password); - } - $scope.currentStep++; - $scope.close(""); - }); - } + $scope.signup = () => { + if ($scope.signupForm.$valid) { + $scope.working = true; + $scope.createWallet((uid) => { + $scope.working = false; + if (uid != null) { + $cookies.put("uid", uid); + } + if ($scope.savePassword) { + $cookies.put("password", $scope.fields.password); + } + $scope.close(""); + }); } }; $scope.createWallet = successCallback => { - Wallet.create($scope.fields.password, $scope.fields.email, $scope.fields.language, $scope.fields.currency, uid => { - $cookieStore.put("uid", uid); + Wallet.create($scope.fields.password, $scope.fields.email, $scope.fields.language, $scope.fields.currency, (uid) => { successCallback(uid); }); }; - $scope.$watch("fields.confirmation", newVal => { - if ((newVal != null) && $scope.fields.password !== "") { - $scope.validate(false); - } - }); - - $scope.validate = visual => { - if (visual == null) { - visual = true; - } - $scope.isValid[0] = true; - $scope.isValid[1] = true; - $scope.errors = { - email: null, - password: null, - confirmation: null - }; - $scope.success = { - email: false, - password: false, - confirmation: false - }; - if ($scope.fields.email === "") { - $scope.isValid[0] = false; - $translate("EMAIL_ADDRESS_REQUIRED").then( translation => { - $scope.errors.email = translation; - }); - } else if ($scope.form && $scope.form.$error.email) { - $scope.isValid[0] = false; - $translate("EMAIL_ADDRESS_INVALID").then( translation => { - $scope.errors.email = translation; - }); - } else { - $scope.success.email = true; - } - if ($scope.form && $scope.form.$error) { - if ($scope.form.$error.minEntropy) { - $scope.isValid[0] = false; - $translate("TOO_WEAK").then( translation => { - $scope.errors.password = translation; - }); - } - if ($scope.form.$error.maxlength) { - $scope.isValid[0] = false; - $translate("TOO_LONG").then( translation => { - $scope.errors.password = translation; - }); - } - } - if ($scope.fields.confirmation === "") { - $scope.isValid[0] = false; - } else { - if ($scope.fields.confirmation === $scope.fields.password) { - $scope.success.confirmation = true; - } else { - $scope.isValid[0] = false; - if (visual) { - $translate("NO_MATCH").then( translation => { - $scope.errors.confirmation = translation; - }); - } - } - } - if (!$scope.fields.acceptedAgreement) { - $scope.isValid[0] = false; - } - }; - $scope.validate(); - - $scope.$watch("fields.language", (newVal, oldVal) => { - if (newVal != null) { + $scope.$watch("language_guess", (newVal, oldVal) => { + if (newVal) { $translate.use(newVal.code); Wallet.changeLanguage(newVal); } }); - $scope.$watch("fields.currency", (newVal, oldVal) => { - if (newVal != null) { - Wallet.changeCurrency(newVal); - } - }); - - $scope.$on('signed_agreement', () => { - $scope.fields.acceptedAgreement = true; + $scope.$watch("currency_guess", (newVal, oldVal) => { + if (newVal) Wallet.changeCurrency(newVal); }); } diff --git a/assets/js/controllers/transaction.controller.js b/assets/js/controllers/transaction.controller.js index 9225cf743e..6bd8fe4f63 100644 --- a/assets/js/controllers/transaction.controller.js +++ b/assets/js/controllers/transaction.controller.js @@ -2,7 +2,7 @@ angular .module('walletApp') .controller('TransactionCtrl', TransactionCtrl); -function TransactionCtrl($scope, Wallet, $log, $state, $stateParams, $filter, $cookieStore, $sce) { +function TransactionCtrl($scope, Wallet, $log, $state, $stateParams, $filter, $sce) { $scope.addressBook = Wallet.addressBook; $scope.status = Wallet.status; $scope.settings = Wallet.settings; diff --git a/assets/js/controllers/unsubscribe.controller.js b/assets/js/controllers/unsubscribe.controller.js new file mode 100644 index 0000000000..9d1dac2e68 --- /dev/null +++ b/assets/js/controllers/unsubscribe.controller.js @@ -0,0 +1,31 @@ +angular + .module('walletApp') + .controller("UnsubscribeCtrl", UnsubscribeCtrl); + +function UnsubscribeCtrl($scope, Wallet, $stateParams, $state, Alerts, $translate) { + const success = (uid) => { + + if(uid) { + $translate('UNSUBSCRIBE_SUCCESS').then(translation => { + $state.go("public.login-uid", {uid: uid}).then(() => { + Alerts.displaySuccess(translation) + }); + }); + } else { + $translate('UNSUBSCRIBE_SUCCESS').then(translation => { + $state.go("public.login-no-uid").then(() => { + Alerts.displaySuccess(translation) + }); + }); + } + + + } + + const error = (message) => { + $state.go("public.login-no-uid"); + Alerts.displayError(message, true); + } + + Wallet.unsubscribe($stateParams.token).then(success).catch(error); +} diff --git a/assets/js/controllers/verifyEmail.controller.js b/assets/js/controllers/verifyEmail.controller.js new file mode 100644 index 0000000000..fdaf575c10 --- /dev/null +++ b/assets/js/controllers/verifyEmail.controller.js @@ -0,0 +1,40 @@ +angular + .module('walletApp') + .controller("VerifyEmailCtrl", VerifyEmailCtrl); + +function VerifyEmailCtrl($scope, Wallet, $stateParams, $state, Alerts, $translate, $rootScope) { + const success = (uid) => { + if(uid) { + $translate(['SUCCESS', 'EMAIL_VERIFIED_SUCCESS']).then(translations => { + $state.go("public.login-uid", {uid: uid}).then(() =>{ + $rootScope.$emit('showNotification', { + type: 'verified-email', + icon: 'ti-email', + heading: translations.SUCCESS, + msg: translations.EMAIL_VERIFIED_SUCCESS + }); + }); + }); + } else { + $translate(['SUCCESS', 'EMAIL_VERIFIED_SUCCESS_NO_UID']).then(translations => { + $state.go("public.login-no-uid").then(() => { + $rootScope.$emit('showNotification', { + type: 'verified-email', + icon: 'ti-email', + heading: translations.SUCCESS, + msg: translations.EMAIL_VERIFIED_SUCCESS_NO_UID + }); + }); + }); + } + + + } + + const error = (message) => { + $state.go("public.login-no-uid"); + Alerts.displayError(message, true); + } + + Wallet.verifyEmail($stateParams.token).then(success).catch(error); +} diff --git a/assets/js/controllers/walletNavigation.controller.js b/assets/js/controllers/walletNavigation.controller.js index 8dab6cda75..bab2bddfe1 100644 --- a/assets/js/controllers/walletNavigation.controller.js +++ b/assets/js/controllers/walletNavigation.controller.js @@ -64,10 +64,11 @@ function WalletNavigationCtrl($scope, Wallet, Alerts, SecurityCenter, $state, $s $scope.termsOfService = () => { let modalInstance = $uibModal.open({ - templateUrl: 'partials/terms-of-service.jade', - windowClass: 'bc-modal terms-modal' + templateUrl: "partials/user-agreement.jade", + controller: function () {}, + windowClass: "bc-modal terms-modal" }); - }; + } $scope.privacyPolicy = () => { let modalInstance = $uibModal.open({ diff --git a/assets/js/core/walletTokenEndpoints.service.js b/assets/js/core/walletTokenEndpoints.service.js new file mode 100644 index 0000000000..53032c362b --- /dev/null +++ b/assets/js/core/walletTokenEndpoints.service.js @@ -0,0 +1,7 @@ +angular + .module('walletApp.core') + .factory('MyWalletTokenEndpoints', MyWalletTokenEndpoints); + +function MyWalletTokenEndpoints() { + return Blockchain.WalletTokenEndpoints +} diff --git a/assets/js/directives/bc-async-input.directive.js b/assets/js/directives/bc-async-input.directive.js index 3befda15d1..96764e08b4 100644 --- a/assets/js/directives/bc-async-input.directive.js +++ b/assets/js/directives/bc-async-input.directive.js @@ -74,6 +74,9 @@ function bcAsyncInput($timeout, Wallet) { scope.ngModel = scope.form.newValue; if (!attrs.custom) scope.bcAsyncForm.$setPristine(); + scope.$root.$safeApply(scope) + Wallet.saveActivity(2) + // Fixes issue: hit enter after changing PBKDF2 iterations // when 2nd password is enabled scope.$evalAsync(() => { diff --git a/assets/js/routes.js b/assets/js/routes.js index 5c32532a44..fd86cf55f1 100644 --- a/assets/js/routes.js +++ b/assets/js/routes.js @@ -70,7 +70,7 @@ function AppRouter($stateProvider, $urlRouterProvider) { } } }) - .state('public.login', { + .state('public.login-no-uid', { url: '/login', views: { alerts: commonViews.alerts, @@ -80,6 +80,16 @@ function AppRouter($stateProvider, $urlRouterProvider) { } } }) + .state('public.login-uid', { + url: '/login/:uid', + views: { + alerts: commonViews.alerts, + contents: { + templateUrl: 'partials/login.jade', + controller: 'LoginCtrl' + } + } + }) .state('public.signup', { url: '/signup', views: { @@ -119,6 +129,36 @@ function AppRouter($stateProvider, $urlRouterProvider) { } } }) + .state('public.reset-two-factor', { + url: '/reset-2fa', + views: { + alerts: commonViews.alerts, + contents: { + templateUrl: 'partials/reset-two-factor.jade', + controller: 'ResetTwoFactorCtrl' + } + } + }) + .state('public.authorize-approve', { + url: '/authorize-approve/{token:.*}', + views: { + alerts: commonViews.alerts, + contents: { + templateUrl: 'partials/authorize-approve.jade', + controller: 'AuthorizeApproveCtrl' + } + } + }) + .state('public.reset-two-factor-token', { + url: '/reset-two-factor/{token:.*}', + views: { + alerts: commonViews.alerts, + contents: { + templateUrl: 'partials/reset-two-factor-token.jade', + controller: 'ResetTwoFactorTokenCtrl' + } + } + }) .state('signup.finish', { url: '/signup/finish', views: commonViews @@ -223,6 +263,26 @@ function AppRouter($stateProvider, $urlRouterProvider) { } } }) + .state('wallet.common.verify-email', { + url: '/verify-email/{token:.*}', + views: { + top: top, + left: walletNav, + right: { + controller: 'VerifyEmailCtrl' + } + } + }) + .state('wallet.common.unsubscribe', { + url: '/unsubscribe/{token:.*}', + views: { + top: top, + left: walletNav, + right: { + controller: 'UnsubscribeCtrl' + } + } + }) .state('wallet.common.settings', { url: '/settings', views: { @@ -267,7 +327,7 @@ function AppRouter($stateProvider, $urlRouterProvider) { } }) .state('wallet.common.settings.accounts_index', { - url: '/accounts', + url: '/addresses', views: { settings: { templateUrl: 'partials/settings/accounts.jade', diff --git a/assets/js/services/adverts.service.js b/assets/js/services/adverts.service.js index 1486d94483..27b048f2b1 100644 --- a/assets/js/services/adverts.service.js +++ b/assets/js/services/adverts.service.js @@ -2,9 +2,9 @@ angular .module('adverts', []) .factory('Adverts', Adverts); -Adverts.$inject = ['$http']; +Adverts.$inject = ['$http', '$rootScope']; -function Adverts($http) { +function Adverts($http, $rootScope) { const service = { ads : [], didFetch : false, @@ -21,7 +21,7 @@ function Adverts($http) { } function fetch() { - let advertsFeed = 'https://blockchain.info/adverts_feed?wallet_version=3'; + let advertsFeed = $rootScope.rootURL + 'adverts_feed?wallet_version=3'; $http.get(advertsFeed) .success(data => { let adverts = data.partners.home_buttons.splice(0); diff --git a/assets/js/services/wallet.service.js b/assets/js/services/wallet.service.js index 86d79c92fe..6650b61880 100644 --- a/assets/js/services/wallet.service.js +++ b/assets/js/services/wallet.service.js @@ -9,9 +9,9 @@ angular .module('walletServices', []) .factory('Wallet', Wallet); -Wallet.$inject = ['$http', '$window', '$timeout', 'Alerts', 'MyWallet', 'MyBlockchainApi', 'MyBlockchainSettings', 'MyWalletStore', 'MyWalletPayment', '$rootScope', 'ngAudio', '$cookieStore', '$translate', '$filter', '$state', '$q', 'bcPhoneNumber', 'languages', 'currency']; +Wallet.$inject = ['$http', '$window', '$timeout', 'Alerts', 'MyWallet', 'MyBlockchainApi', 'MyBlockchainSettings', 'MyWalletStore', 'MyWalletPayment', 'MyWalletTokenEndpoints', '$rootScope', 'ngAudio', '$cookies', '$translate', '$filter', '$state', '$q', 'bcPhoneNumber', 'languages', 'currency']; -function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyBlockchainSettings, MyWalletStore, MyWalletPayment, $rootScope, ngAudio, $cookieStore, $translate, $filter, $state, $q, bcPhoneNumber, languages, currency) { +function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyBlockchainSettings, MyWalletStore, MyWalletPayment, MyWalletTokenEndpoints, $rootScope, ngAudio, $cookies, $translate, $filter, $state, $q, bcPhoneNumber, languages, currency) { const wallet = { goal: { auth: false @@ -56,8 +56,24 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.my = MyWallet; wallet.settings_api = MyBlockchainSettings; wallet.store = MyWalletStore; + wallet.api = MyBlockchainApi; + + // If customRootURL is set by index.jade: + // Grunt can replace this: + const customRootURL = $rootScope.rootURL || "/"; + wallet.api.ROOT_URL=customRootURL; + // If customRootURL is set by Grunt: + $rootScope.rootURL = customRootURL; + + // Grunt can replace this: + const customWebSocketURL = $rootScope.webSocketURL; + if(customWebSocketURL) { + wallet.my.ws.wsUrl=customWebSocketURL; + } + wallet.payment = MyWalletPayment; + wallet.tokenEndpoints = MyWalletTokenEndpoints; wallet.transactions = []; wallet.api_code = '1770d5d9-bcea-4d28-ad21-6cbd5be018a8'; @@ -122,12 +138,12 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB }; wallet.my.wallet.getHistory().then(didFetchTransactions); } - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); if (successCallback != null) { successCallback(); } - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let needsTwoFactorCode = (method) => { @@ -141,12 +157,12 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB needsTwoFactorCallback(); wallet.settings.twoFactorMethod = method; - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let wrongTwoFactorCode = (message) => { errorCallback('twoFactor', message); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let loginError = (error) => { @@ -159,7 +175,7 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB Alerts.displayError(error, true); errorCallback(); } - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; if (two_factor_code != null && two_factor_code !== '') { wallet.settings.needs2FA = true; @@ -169,54 +185,31 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB let authorizationProvided = () => { wallet.goal.auth = true; - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let authorizationRequired = (callback) => { callback(authorizationProvided()); Alerts.displayWarning('Please check your email to approve this login attempt.', true); - wallet.applyIfNeeded(); - }; - - let betaCheckFinished = () => { - $window.root = 'https://blockchain.info/'; - wallet.my.login( - uid, - null, // sharedKey - password, - two_factor_code, - didLogin, - needsTwoFactorCode, - wrongTwoFactorCode, - authorizationRequired, - loginError, - () => {}, // fetchSuccess - () => {}, // decryptSucces - () => {} // buildHDSucces - ); - currency.fetchExchangeRate(); + $rootScope.$safeApply(); }; - // If BETA=1 is set in .env then in index.html/jade $rootScope.beta is set. - if ($rootScope.beta) { - $http.post('/check_guid_for_beta_key', { - guid: uid - }).success((data) => { - if (data.verified) { - betaCheckFinished(); - } else { - if (data.error && data.error.message) { - Alerts.displayError(data.error.message); - } - errorCallback(); - } - }).error(() => { - Alerts.displayError('Unable to verify your wallet UID.'); - errorCallback(); - }); - } else { - betaCheckFinished(); - } + $window.root = 'https://blockchain.info/'; + wallet.my.login( + uid, + null, // sharedKey + password, + two_factor_code, + didLogin, + needsTwoFactorCode, + wrongTwoFactorCode, + authorizationRequired, + loginError, + () => {}, // fetchSuccess + () => {}, // decryptSucces + () => {} // buildHDSucces + ); + currency.fetchExchangeRate(); }; wallet.upgrade = (successCallback, cancelSecondPasswordCallback) => { @@ -228,7 +221,7 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.updateTransactions(); }); successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let error = () => { @@ -272,7 +265,7 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB let success = () => { wallet.hdAddresses(account.index)(true); successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; $translate('DEFAULT_NEW_ADDRESS_LABEL').then((translation) => { account.setLabelForReceivingAddress(account.receiveIndex, translation) @@ -284,16 +277,94 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB let success = () => { $translate('RESENT_2FA_SMS').then(Alerts.displaySuccess); successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let error = (e) => { $translate('RESENT_2FA_SMS_FAILED').then(Alerts.displayError); errorCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; wallet.my.resendTwoFactorSms(uid, success, error); }; + wallet.recoverGuid = (email, captcha) => { + let defer = $q.defer() + let success = (message) => { + Alerts.displaySuccess(message); + defer.resolve(); + $rootScope.$safeApply(); + }; + let error = (error) => { + + switch (error) { + case 'Captcha Code Incorrect': + Alerts.displayError($translate.instant('CAPTCHA_INCORRECT')); + break; + case 'Quota Exceeded': + Alerts.displayError($translate.instant('QUOTA_EXCEEDED')); + break; + default: + Alerts.displayError($translate.instant('UNKNOWN_ERROR')); + } + + defer.reject(); + $rootScope.$safeApply(); + }; + wallet.my.recoverGuid(email, captcha).then(success).catch(error); + return defer.promise; + }; + + wallet.requestTwoFactorReset = (guid, email, new_email, secret, message, captcha) => { + let defer = $q.defer() + + Alerts.clear() + let success = (message) => { + Alerts.displaySuccess(message); + defer.resolve(); + $rootScope.$safeApply(); + }; + let error = (error) => { + switch (error) { + case 'Captcha Code Incorrect': + Alerts.displayError($translate.instant('CAPTCHA_INCORRECT'), true); + break; + case 'Quota Exceeded': + Alerts.displayError($translate.instant('QUOTA_EXCEEDED'), true); + break; + default: + Alerts.displayError(error, true); + } + + defer.reject(); + $rootScope.$safeApply(); + }; + wallet.my.requestTwoFactorReset(guid, email, new_email, secret, message, captcha) + .then(success) + .catch(error); + + return defer.promise; + }; + + wallet.resetTwoFactorToken = (token) => { + let defer = $q.defer() + + const success = (obj) => { + defer.resolve(obj); + $rootScope.$safeApply(); + } + + const error = (e) => { + defer.reject(e.error); + $rootScope.$safeApply(); + } + + wallet.tokenEndpoints.resetTwoFactor(token) + .then(success) + .catch(error); + + return defer.promise; + } + wallet.create = (password, email, currency, language, success_callback) => { let success = (uid) => { Alerts.displaySuccess('Wallet created with identifier: ' + uid, true); @@ -308,24 +379,7 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB Alerts.displayError('Unable to login to new wallet'); }; - if ($rootScope.beta) { - $http.post('/register_guid', { - guid: uid, - email: email - }).success((data) => { - if (data.success) { - wallet.login(uid, password, null, null, loginSuccess, loginError); - } else { - if (data.error && data.error.message) { - Alerts.displayError(data.error.message); - } - } - }).error(() => { - Alerts.displayWarning('Unable to associate your new wallet with your invite code. Please try to login using your UID ' + uid + ' or register again.', true); - }); - } else { - wallet.login(uid, password, null, null, loginSuccess, loginError); - } + wallet.login(uid, password, null, null, loginSuccess, loginError); }; let error = (error) => { @@ -403,17 +457,17 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB let success = (res) => { wallet.appendTransactions(res); successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let error = () => { errorCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let allTransactionsLoaded = () => { allTransactionsLoadedCallback && allTransactionsLoadedCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; if (where === '') { @@ -434,12 +488,12 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB let success = () => { wallet.hdAddresses(accountIdx)(true); successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let error = (msg) => { errorCallback(msg); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let account = wallet.accounts()[parseInt(accountIdx)]; @@ -456,12 +510,12 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.makePairingCode = (successCallback, errorCallback) => { let success = (code) => { successCallback(code); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let error = () => { errorCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; wallet.my.makePairingCode(success, error); @@ -493,12 +547,12 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB let success = () => { wallet.settings.ipWhitelist = ips; successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let error = () => { errorCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; wallet.settings_api.update_IP_lock(ips, success, error); @@ -507,12 +561,12 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.resendEmailConfirmation = (successCallback, errorCallback) => { let success = () => { successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let error = () => { errorCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; wallet.settings_api.resendEmailConfirmation(wallet.user.email, success, error); @@ -530,10 +584,10 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.settings_api.updateLoggingLevel(level, () => { wallet.settings.loggingLevel = level; wallet.saveActivity(4); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }, () => { Alerts.displayError('Failed to update logging level'); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); }; @@ -560,7 +614,7 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.addAddressOrPrivateKey = (addressOrPrivateKey, needsBipPassphraseCallback, successCallback, errorCallback, cancel) => { let success = (address) => { successCallback(address); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let proceed = (secondPassword='') => { @@ -570,7 +624,7 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB } else { errorCallback(message); } - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let proceedWithBip38 = (bipPassphrase) => { @@ -782,7 +836,7 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB } } wallet.status.didLoadTransactions = true; - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; wallet.appendTransactions = (transactions, override) => { @@ -843,16 +897,16 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB Alerts.displayError(translation); }); } else if (event === 'ticker_updated' || event === 'did_set_latest_block') { - wallet.applyIfNeeded(); + $rootScope.$safeApply(); } else if (event === 'logging_out') { if (wallet.didLogoutByChoice) { $translate('LOGGED_OUT').then((translation) => { - $cookieStore.put('alert-success', translation); + $cookies.put('alert-success', translation); }); } else { $translate('LOGGED_OUT_AUTOMATICALLY').then((translation) => { - $cookieStore.put('alert-warning', translation); - wallet.applyIfNeeded(); + $cookies.put('alert-warning', translation); + $rootScope.$safeApply(); }); } wallet.status.isLoggedIn = false; @@ -868,13 +922,13 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB } else if (event.type !== void 0) { if (event.type === 'error') { Alerts.displayError(event.msg); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); } else if (event.type === 'success') { Alerts.displaySuccess(event.msg); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); } else if (event.type === 'notice') { Alerts.displayWarning(event.msg); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); } else { } } else { @@ -885,15 +939,15 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.monitor(event, data); }); - let message = $cookieStore.get('alert-warning'); + let message = $cookies.get('alert-warning'); if (message !== void 0 && message !== null) { Alerts.displayWarning(message, true); - $cookieStore.remove('alert-warning'); + $cookies.remove('alert-warning'); } - message = $cookieStore.get('alert-success'); + message = $cookies.get('alert-success'); if (message !== void 0 && message !== null) { Alerts.displaySuccess(message); - $cookieStore.remove('alert-success'); + $cookies.remove('alert-success'); } wallet.setNote = (tx, text) => { @@ -937,11 +991,11 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.user.email = email; wallet.user.isEmailVerified = false; successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }), () => { $translate('CHANGE_EMAIL_FAILED').then((translation) => { Alerts.displayError(translation); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); errorCallback(); }); @@ -949,11 +1003,11 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.enableNotifications = () => { let success = () => { wallet.settings.notifications = true; - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let error = () => { Alerts.displayError('Failed to enable notifications'); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; wallet.my.wallet.enableNotifications(success, error); }; @@ -961,11 +1015,11 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.disableNotifications = () => { let success = () => { wallet.settings.notifications = false; - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let error = () => { Alerts.displayError('Failed to disable notifications'); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; wallet.my.wallet.disableNotifications(success, error); }; @@ -994,13 +1048,13 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.user.mobile = mobile; wallet.user.isMobileVerified = false; successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }), () => { $translate('CHANGE_MOBILE_FAILED').then((translation) => { Alerts.displayError(translation); }); errorCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); }; @@ -1008,29 +1062,23 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.settings_api.verifyMobile(code, (() => { wallet.user.isMobileVerified = true; successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }), () => { $translate('VERIFY_MOBILE_FAILED').then((translation) => { errorCallback(translation); }); - wallet.applyIfNeeded(); - }); - }; - - wallet.applyIfNeeded = () => { - if (MyWallet.mockShouldReceiveNewTransaction === void 0) { $rootScope.$safeApply(); - } + }); }; wallet.changePasswordHint = (hint, successCallback, errorCallback) => { wallet.settings_api.update_password_hint1(hint, (() => { wallet.user.passwordHint = hint; successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }), (err) => { errorCallback(err); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); }; @@ -1040,10 +1088,10 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.settings_api.unsetTwoFactor(() => { wallet.settings.needs2FA = false; wallet.settings.twoFactorMethod = null; - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }, () => { console.log('Failed'); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); }; @@ -1051,10 +1099,10 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.settings_api.setTwoFactorSMS(() => { wallet.settings.needs2FA = true; wallet.settings.twoFactorMethod = 5; - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }, () => { console.log('Failed'); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); }; @@ -1062,10 +1110,10 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.settings_api.setTwoFactorEmail(() => { wallet.settings.needs2FA = true; wallet.settings.twoFactorMethod = 2; - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }, () => { console.log('Failed'); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); }; @@ -1074,21 +1122,21 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.settings.needs2FA = true; wallet.settings.twoFactorMethod = 1; successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }, (error) => { console.log(error); errorCallback(error); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); }; wallet.setTwoFactorGoogleAuthenticator = () => { wallet.settings_api.setTwoFactorGoogleAuthenticator((secret) => { wallet.settings.googleAuthenticatorSecret = secret; - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }, () => { console.log('Failed'); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); }; @@ -1098,10 +1146,10 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.settings.twoFactorMethod = 4; wallet.settings.googleAuthenticatorSecret = null; successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }, () => { errorCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); }; @@ -1109,11 +1157,11 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB let success = () => { wallet.settings.rememberTwoFactor = true; successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let error = () => { errorCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; wallet.settings_api.toggleSave2FA(false, success, error); }; @@ -1122,11 +1170,11 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB let success = () => { wallet.settings.rememberTwoFactor = false; successCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; let error = () => { errorCallback(); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }; wallet.settings_api.toggleSave2FA(true, success, error); }; @@ -1139,20 +1187,20 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.enableBlockTOR = () => { wallet.settings_api.update_tor_ip_block(1, () => { wallet.settings.blockTOR = true; - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }, () => { console.log('Failed'); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); }; wallet.disableBlockTOR = () => { wallet.settings_api.update_tor_ip_block(0, () => { wallet.settings.blockTOR = false; - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }, () => { console.log('Failed'); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); }; @@ -1160,10 +1208,10 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.settings_api.update_IP_lock_on(true, () => { wallet.settings.restrictToWhitelist = true; wallet.saveActivity(2); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }, () => { console.log('Failed'); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); }; @@ -1171,10 +1219,10 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.settings_api.update_IP_lock_on(false, () => { wallet.settings.restrictToWhitelist = false; wallet.saveActivity(2); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }, () => { console.log('Failed'); - wallet.applyIfNeeded(); + $rootScope.$safeApply(); }); }; @@ -1237,6 +1285,73 @@ function Wallet($http, $window, $timeout, Alerts, MyWallet, MyBlockchainApi, MyB wallet.my.wallet.encrypt(password, success, error, encrypting, syncing); }; + wallet.verifyEmail = (token) => { + let defer = $q.defer(); + + const success = (res) => { + wallet.user.isEmailVerified = true; + defer.resolve(res.guid); + $rootScope.$safeApply(); + } + + const error = (res) => { + console.log(res.error); + defer.reject(res.error); + $rootScope.$safeApply(); + } + + wallet.tokenEndpoints.verifyEmail(token) + .then(success) + .catch(error); + + return defer.promise; + } + + wallet.unsubscribe = (token) => { + let defer = $q.defer(); + + const success = (res) => { + defer.resolve(res.guid); + $rootScope.$safeApply(); + } + + const error = (res) => { + console.log(res.error); + defer.reject(res.error); + $rootScope.$safeApply(); + } + + wallet.tokenEndpoints.unsubscribe(token).then(success).catch(error); + + return defer.promise; + } + + wallet.authorizeApprove = (token, differentBrowserCallback, differentBrowserApproved) => { + let defer = $q.defer() + + const success = (res) => { + defer.resolve(res.guid); + $rootScope.$safeApply(); + } + + const error = (res) => { + console.log(res.error); + defer.reject(res.error); + $rootScope.$safeApply(); + } + + const differentBrowser = (details) => { + differentBrowserCallback(details); + $rootScope.$safeApply(); + } + + wallet.tokenEndpoints.authorizeApprove(token, differentBrowser, differentBrowserApproved) + .then(success) + .catch(error); + + return defer.promise; + } + // Testing: only works on mock MyWallet wallet.refresh = () => { diff --git a/betakeys-template.MDF b/betakeys-template.MDF deleted file mode 100644 index 407d5614da..0000000000 Binary files a/betakeys-template.MDF and /dev/null differ diff --git a/bower.json b/bower.json index 401481e58a..a9e233679e 100644 --- a/bower.json +++ b/bower.json @@ -18,12 +18,12 @@ "angular-qr": "0.2.*", "angular-translate": "2.7.*", "angular-translate-loader-static-files": "2.7.*", - "angular-inview": "1.5.0", + "angular-inview": "1.5.*", "angular-password-entropy": "0.1.*", "browserdetection": "0.3.*", "bc-qr-reader": "0.2.*", "bootstrap-sass": "3.3.*", - "blockchain-wallet": "3.3.*", + "blockchain-wallet": "3.5.*", "bc-phone-number": "5.0.*" }, "devDependencies": { diff --git a/check_translations.rb b/check_translations.rb index 5efeb07259..0bd6abd873 100755 --- a/check_translations.rb +++ b/check_translations.rb @@ -3,8 +3,8 @@ puts "Don't forget to run 'grunt dist' or 'grunt dist_unsafe' first..." -js_file = Dir.entries("dist").keep_if{|entry| entry.include?("application-") && entry.include?(".js") } -js = File.read("dist/" + js_file[0]) +js_file = Dir.entries("dist/js").keep_if{|entry| entry.include?("application-") } +js = File.read("dist/js/" + js_file[0]) orphaned = false for key, string in JSON.parse(File.read('locales/en-human.json')) diff --git a/dependency-whitelist.json b/dependency-whitelist.json index 2fb25835be..e4dcfc6c91 100644 --- a/dependency-whitelist.json +++ b/dependency-whitelist.json @@ -43,8 +43,8 @@ "repo" : "angular-translate/bower-angular-translate-loader-static-files" }, "angular-inview" : { - "version": "1.5.0", - "commits" : ["a4a3ce66181979a34a004d996982199f5bf69d32"], + "version": "1.5.6", + "commits" : ["96cb2f24b62849632ffdcb62cbb679de594d7629"], "repo" : "thenikso/angular-inview" }, "angular-password-entropy" : { diff --git a/e2e-tests/config.js b/e2e-tests/config.js deleted file mode 100755 index b5118db996..0000000000 --- a/e2e-tests/config.js +++ /dev/null @@ -1,46 +0,0 @@ -exports.config = { - - // The address of a running selenium server - seleniumAddress: 'http://localhost:4444/wd/hub', - - // Spec patterns are relative to the location of this config - specs: ['**/*_spec.js'], - - suites: { - trans: '**/transactions/*_spec.js', - global: '**/global/*_spec.js', - settings: '**/settings/*_spec.js', - securityCenter: '**/security-center/*_spec.js' - }, - - params: { - login: { - uidfake: 'c5825g04-8ke3-25r1-p103-3g000wr4-123', - pwweak: 'asdf', - pwregular: 'asdfgh123456', - pwnormal: 'asdf!@#$', - email: 'example@example.com', - nums: '1234567890', - chars: '$^*%(^*#$&@', - } - }, - - jasmineNodeOpts: { - showColors: true, - isVerbose: true, - includeStackTrace: true - }, - - onPrepare: function() { - browser.driver.manage().window().setSize(1400, 1200); - }, - - // Capabilities to be passed to the webdriver instance - multiCapabilities: [{ - 'browserName': 'chrome' - // }, { - // 'browserName': 'firefox' - }] - -} - diff --git a/e2e-tests/global/login_spec.js b/e2e-tests/global/login_spec.js deleted file mode 100755 index b8dad79f12..0000000000 --- a/e2e-tests/global/login_spec.js +++ /dev/null @@ -1,170 +0,0 @@ -describe('login-page', function() { - - // Required JS files - var login = require('../ignore.js'); - var util = require('../util.js'); - - // These are log in fields - var loginUIDField = browser.element(by.model('uid')); - var loginPasswordField = browser.element(by.model('password')); - - // These are create login fields - var passwordField = element(by.model('fields.password')); - var confField = element(by.model('fields.confirmation')); - var emailField = element(by.model('fields.email')); - - // These are log in buttons - var loginButton = element(by.id('login')); - - // Clear log in test fields - var clearLoginFields = function (){ - loginUIDField.clear(); - loginPasswordField.clear(); - } - - // Click field, see invalid error, clear email field - var validateInvalid = function(){ - passwordField.click(); - util.shouldContainCSS('p.ng-binding', 'Invalid'); - emailField.clear(); - } - - beforeEach(function() { - - util.getURL(); - - }); - - afterEach(function() { - - // Refresh to begin test on login page - browser.refresh(); - - }); - - xit('should validate top navigation items', function() { - - browser.findElement(by.id('logo')); - browser.findElement(by.linkText('Home')); - browser.findElement(by.linkText('About')); - browser.findElement(by.linkText('Wallet')); - browser.findElement(by.linkText('Explorer')); - browser.findElement(by.linkText('Merchant')); - browser.findElement(by.linkText('Support')); - - }); - - it('should have login page elements', function() { - - // Find UID label and text field - browser.findElement(by.css('[translate="UID"]')); - browser.findElement(by.model('uid')); - - // Find password label and text field - browser.findElement(by.css('[translate="PASSWORD"]')); - browser.findElement(by.model('password')); - - // Find login and create wallet buttons - browser.findElement(by.id('login')); - browser.findElement(by.css('[ng-click="register()"]')); - - }); - - it('should test password strength', function() { - - util.submitBetaKey(); - - // Test weak password - passwordField.sendKeys(browser.params.login.pwweak); - browser.findElement(by.css('.progress-bar-danger')); - passwordField.clear(); - - // Test regular password - passwordField.sendKeys(browser.params.login.pwregular); - browser.findElement(by.css('.progress-bar-warning')); - passwordField.clear(); - - // Test normal password - passwordField.sendKeys(browser.params.login.pwnormal); - browser.findElement(by.css('.progress-bar-info')); - passwordField.clear(); - - // Test strong password - passwordField.sendKeys(login.pw); - browser.findElement(by.css('.progress-bar-success')); - passwordField.clear(); - - }); - - it('should test password matching', function() { - - // Enter key and click Create Wallet button - util.submitBetaKey(); - - // Enter non-matching passwords - passwordField.sendKeys(login.pw); - confField.sendKeys(browser.params.login.pwweak); - - // Submit and validate error messaging - passwordField.click(); - util.shouldContainCSS('p.ng-binding', 'Does not match'); - - // Clear both fields - passwordField.clear(); - confField.clear(); - - }); - - // Invite key pre-fills email address and disables editing, this test case is disabled - xit('should test email address validation', function() { - - // Enter beta key and click Create Wallet button - util.submitBetaKey(); - - // Test email address with only letters - emailField.sendKeys(browser.params.login.pwweak); - validateInvalid(); - - // Test email address with only numbers - emailField.sendKeys(browser.params.login.nums); - validateInvalid(); - - // Test email address with only special characters - emailField.sendKeys(browser.params.login.chars); - validateInvalid(); - - }); - - it('should test unsuccessful login, invalid UID', function() { - - // Clear log in text fields - clearLoginFields(); - - // Enter bogus wallet identifier and password - loginUIDField.sendKeys(browser.params.login.uidfake); - loginPasswordField.sendKeys(login.pw); - - // Submit and validate error messaging - loginButton.click(); - // TODO This element isn't being found - //util.shouldContainCSS('alert.ng-isolate-scope.alert-danger.alert-dismissable', 'This wallet is not associated with a beta invite key.'); - - }); - - it('should test unsuccessful login, invalid password', function() { - - // Clear log in text fields - clearLoginFields(); - - // Enter valid wallet identifier and incorrect password - loginUIDField.sendKeys(login.uid); - loginPasswordField.sendKeys(browser.params.login.pwweak); - - // Submit and validate error messaging - loginButton.click(); - browser.sleep(3000); - util.shouldContainCSS('.help-block', 'Error Decrypting Wallet. Please check your password is correct.'); - - }); - -}); diff --git a/e2e-tests/security-center/block_tor_spec.js b/e2e-tests/security-center/block_tor_spec.js deleted file mode 100644 index 6ca9065923..0000000000 --- a/e2e-tests/security-center/block_tor_spec.js +++ /dev/null @@ -1,48 +0,0 @@ -describe('block-tor', function() { - // Required JS files - var login = require('../ignore.js'); - var util = require('../util.js'); - - - beforeEach(function() { - util.getURL(); - util.logIn(); - util.navigateTo("SECURITY"); - browser.sleep(4000); - browser.findElement(by.cssContainingText('.ng-binding', 'Tor Blocked')).isDisplayed().then(function(result) { - if (result) { - util.navigateTo("SETTINGS"); - util.navigateTo("ADVANCED"); - browser.sleep(1000);//wait for animation - browser.findElement(by.css('[translate="DISABLE_BLOCK_TOR"]')).click(); - browser.findElement(by.css('[translate="HOME"]')).click(); - util.navigateTo("SECURITY"); - } - else { - util.navigateTo("SECURITY"); - } - }); - }); - - afterEach(function() { - - browser.refresh(); - - }); - - it('should be able to block tor', function() { - - - browser.findElement(by.css('[translate="ADD_BLOCK_TOR"]')).click(); - browser.findElement(by.css('[translate="BLOCK_TOR_EXPLAIN"]')) - - //enable the button - browser.findElement(by.css('[ng-click="enableBlockTOR()"]')).click(); - - //wait for animation to complete - browser.sleep(2000); - browser.findElement(by.cssContainingText('.ng-binding', 'Tor Blocked')) - }); - -}); - diff --git a/e2e-tests/settings/accounts_spec.js b/e2e-tests/settings/accounts_spec.js deleted file mode 100755 index ae6888f15a..0000000000 --- a/e2e-tests/settings/accounts_spec.js +++ /dev/null @@ -1,35 +0,0 @@ -describe('accounts-page', function() { - - // Required JS files - var login = require('../ignore.js'); - var util = require('../util.js'); - - - beforeEach(function() { - - util.getURL(); - util.logIn(); - util.validateHome(); - - // Navigate to Settings and validate page - browser.findElement(by.css('[translate="SETTINGS"]')).click(); - browser.findElement(by.css('[translate="WALLET_SETTINGS_EXPLAIN"]')); - - // Navigate to Wallet Settings and validate page - browser.findElement(by.css('[translate="MY_ACCOUNTS"]')).click(); - browser.findElement(by.css('[translate="ACCOUNT_MANAGEMENT_EXPLAIN"]')); - - }); - - afterEach(function() { - - // Refresh to begin test on login page - browser.refresh(); - - }); - - xit('should have page elements', function() { - - }); - -}); \ No newline at end of file diff --git a/e2e-tests/settings/advanced_spec.js b/e2e-tests/settings/advanced_spec.js deleted file mode 100755 index 31446fa5e7..0000000000 --- a/e2e-tests/settings/advanced_spec.js +++ /dev/null @@ -1,35 +0,0 @@ -describe('advanced-page', function() { - - // Required JS files - var login = require('../ignore.js'); - var util = require('../util.js'); - - - beforeEach(function() { - - util.getURL(); - util.logIn(); - util.validateHome(); - - // Navigate to Settings and validate page - browser.findElement(by.css('[translate="SETTINGS"]')).click(); - browser.findElement(by.css('[translate="WALLET_SETTINGS_EXPLAIN"]')); - - // Navigate to Wallet Settings and validate page - browser.findElement(by.css('[translate="ADVANCED"]')).click(); - browser.findElement(by.css('[translate="ADVANCED_EXPLAIN"]')); - - }); - - afterEach(function() { - - // Refresh to begin test on login page - browser.refresh(); - - }); - - xit('should have page elements', function() { - - }); - -}); \ No newline at end of file diff --git a/e2e-tests/settings/my_addresses_spec.js b/e2e-tests/settings/my_addresses_spec.js deleted file mode 100755 index af08e6e9cd..0000000000 --- a/e2e-tests/settings/my_addresses_spec.js +++ /dev/null @@ -1,35 +0,0 @@ -describe('my-addresses-page', function() { - - // Required JS files - var login = require('../ignore.js'); - var util = require('../util.js'); - - - beforeEach(function() { - - util.getURL(); - util.logIn(); - util.validateHome(); - - // Navigate to Settings and validate page - browser.findElement(by.css('[translate="SETTINGS"]')).click(); - browser.findElement(by.css('[translate="WALLET_SETTINGS_EXPLAIN"]')); - - // Navigate to Wallet Settings and validate page - browser.findElement(by.css('[translate="MY_ADDRESSES"]')).click(); - browser.findElement(by.css('[translate="MY_ADDRESSES_EXPLAIN"]')); - - }); - - afterEach(function() { - - // Refresh to begin test on login page - browser.refresh(); - - }); - - xit('should have page elements', function() { - - }); - -}); diff --git a/e2e-tests/settings/my_details_spec.js b/e2e-tests/settings/my_details_spec.js deleted file mode 100755 index 03075e0e5d..0000000000 --- a/e2e-tests/settings/my_details_spec.js +++ /dev/null @@ -1,97 +0,0 @@ -describe('my-details-page', function() { - - // Required JS files - var login = require('../ignore.js'); - var util = require('../util.js'); - - // My Details specific variables - var passwordHintCSS = '[translate="PASSWORD_HINT"]'; - - beforeEach(function() { - - util.getURL(); - util.logIn(); - util.validateHome(); - - // Navigate to Settings and validate page - browser.findElement(by.css('[translate="SETTINGS"]')).click(); - browser.findElement(by.css('[translate="WALLET_SETTINGS_EXPLAIN"]')); - - // Navigate to My Details and validate page - browser.findElement(by.css('[translate="PREFERENCES"]')).click(); - - // Click is to bring focus to scrollable portion of page - browser.findElement(by.css('[translate="PREFERENCES_EXPLAIN"]')).click(); - - }); - - afterEach(function() { - - // Refresh to begin test on login page - browser.refresh(); - - }); - - - it('should have wallet password elements', function() { - - // Scroll to password hint section - util.scrollTo(passwordHintCSS); - - - // Validate password elements - browser.findElement(by.css('[translate="WALLET_PASSWORD"]')); - browser.findElement(by.css('[translate="WALLET_PASSWORD_EXPLAIN"]')); - browser.findElement(by.css('[translate="PASSWORD_SET"]')); - - }); - - it('should have a wallet password modal', function() { - - // Scroll to password hint section - util.scrollTo(passwordHintCSS); - - // Click Change Button - browser.findElement(by.css('[translate="CHANGE"]')).click(); - - // Validate modal open - browser.findElement(by.css('.modal-content')); - - // Validate modal elements - browser.findElement(by.css('[translate="CURRENT_PASSWORD"]')); - browser.findElement(by.css('[translate="NEW_PASSWORD"]')); - browser.findElement(by.css('[translate="CONFIRM_PASSWORD"]')); - - }); - - it('should change the wallet password hint', function() { - - // Bring focus to scrollable portion - browser.findElement(by.css('[translate="UID"]')).click(); - - // Validate password hint elements - browser.findElement(by.css(passwordHintCSS)); - browser.findElement(by.css('[translate="PASSWORD_HINT_EXPLAIN"]')); - - // Scroll to password hint section - util.scrollTo(passwordHintCSS); - - // Set new password hint - browser.element(by.css('[ng-model="user.passwordHint"]')).element(by.css('[ng-click="edit()"]')).click(); - browser.element(by.css('[ng-model="user.passwordHint"]')).element(by.css('[ng-model="form.newValue"]')).clear(); - browser.element(by.css('[ng-model="user.passwordHint"]')).element(by.css('[ng-model="form.newValue"]')).sendKeys('new password hint'); - browser.element(by.css('[ng-model="user.passwordHint"]')).element(by.css('[type="submit"]')).click(); - browser.sleep(1000); - util.shouldContainCSS('h2.status', 'new password hint'); - - // Reset old password hint - browser.element(by.css('[ng-model="user.passwordHint"]')).element(by.css('[ng-click="edit()"]')).click(); - browser.element(by.css('[ng-model="user.passwordHint"]')).element(by.css('[ng-model="form.newValue"]')).clear(); - browser.element(by.css('[ng-model="user.passwordHint"]')).element(by.css('[ng-model="form.newValue"]')).sendKeys('original password hint'); - browser.element(by.css('[ng-model="user.passwordHint"]')).element(by.css('[type="submit"]')).click(); - browser.sleep(1000); - util.shouldContainCSS('h2.status', 'original password hint'); - - }); - -}); diff --git a/e2e-tests/settings/wallet_recovery_spec.js b/e2e-tests/settings/wallet_recovery_spec.js deleted file mode 100755 index e472b14a31..0000000000 --- a/e2e-tests/settings/wallet_recovery_spec.js +++ /dev/null @@ -1,35 +0,0 @@ -describe('wallet-recovery-page', function() { - - // Required JS files - var login = require('../ignore.js'); - var util = require('../util.js'); - - - beforeEach(function() { - - util.getURL(); - util.logIn(); - util.validateHome(); - - // Navigate to Settings and validate page - browser.findElement(by.css('[translate="SETTINGS"]')).click(); - browser.findElement(by.css('[translate="WALLET_SETTINGS_EXPLAIN"]')); - - // Navigate to Wallet Settings and validate page - browser.findElement(by.css('[translate="WALLET_RECOVERY"]')).click(); - browser.findElement(by.css('[translate="WALLET_RECOVERY_EXPLAIN"]')); - - }); - - afterEach(function() { - - // Refresh to begin test on login page - browser.refresh(); - - }); - - xit('should have page elements', function() { - - }); - -}); \ No newline at end of file diff --git a/e2e-tests/settings/wallet_settings_spec.js b/e2e-tests/settings/wallet_settings_spec.js deleted file mode 100755 index 32e1e077e9..0000000000 --- a/e2e-tests/settings/wallet_settings_spec.js +++ /dev/null @@ -1,42 +0,0 @@ -describe('wallet-settings-page', function() { - - // Required JS files - var login = require('../ignore.js'); - var util = require('../util.js'); - - - beforeEach(function() { - - util.getURL(); - util.logIn(); - util.validateHome(); - - // Navigate to Settings and validate page - browser.findElement(by.css('[translate="SETTINGS"]')).click(); - browser.findElement(by.css('[translate="WALLET_SETTINGS_EXPLAIN"]')).click(); - - }); - - afterEach(function() { - - // Refresh to begin test on login page - browser.refresh(); - - }); - - it('should validate wallet language elements', function() { - - browser.findElement(by.css('[translate="LANGUAGE"]')); - browser.findElement(by.css('[translate="LANGUAGE_EXPLAIN"]')); - browser.findElement(by.css('[language="settings.language"]')); - - }); - - it('should change language settings', function() { - - browser.findElement(by.css('[language="settings.language"]')).findElement(by.css('.ui-select-toggle')).click(); - - - }); - -}); diff --git a/e2e-tests/transactions/transactions_spec.js b/e2e-tests/transactions/transactions_spec.js deleted file mode 100755 index 2da8880c27..0000000000 --- a/e2e-tests/transactions/transactions_spec.js +++ /dev/null @@ -1,188 +0,0 @@ -describe('transactions-page', function() { - - // Required JS files - var login = require('../ignore.js'); - var util = require('../util.js'); - - // Account name variables - var account1Name = 'DONT EDIT 1'; - var account2Name = 'DONT EDIT 2'; - var account3Name = 'DONT EDIT 3'; - var account4Name = 'Spending'; - - // Account transaction value variables - var account1TransValue = '0.00087847 BTC'; - var account1TransValueTx = '0.00077847 BTC'; - var account3TransValue = '0.00042116 BTC'; - var account3TransLocationPage = 'h1.ng-binding'; - - // Account date variables - var account1TransDate = 'April 7 @ 03:51 PM'; - var account3TransDate = 'February 18 @ 06:56 PM'; - - - beforeEach(function() { - - util.getURL(); - util.logIn(); - util.validateHome(); - - }); - - afterEach(function() { - - // Refresh to begin test on login page - browser.refresh(); - - }); - - it('should log out via the "Log Out" button', function() { - - // Open Profile drop down menu, click Logout, and dismiss alert - util.logOut(); - - // Protractor waits 5 seconds until 'Logged Out' message dismisses - - // Find UID label and text field - browser.sleep(4000); //Required wait for UID field - browser.findElement(by.css('[translate="UID"]')); - browser.findElement(by.model('uid')); - - // Find password label and text field - browser.findElement(by.css('[translate="PASSWORD"]')); - browser.findElement(by.model('password')); - - // Find login and create wallet buttons - browser.findElement(by.id('login')); - browser.findElement(by.css('[ng-click="register()"]')); - - }); - - it('should validate account names and balances', function() { - - // Open Transactions drawer in left navigation - browser.findElement(by.css('[translate="MY_TRANSACTIONS"]')).click(); - - // Validate all account names - browser.findElement(by.css('[translate="ALL_ACCOUNTS"]')); - util.shouldContainCSS('[ng-repeat="account in accounts()"]', account1Name); - util.shouldContainCSS('[ng-repeat="account in accounts()"]', account2Name); - util.shouldContainCSS('[ng-repeat="account in accounts()"]', account3Name); - util.shouldContainCSS('[ng-repeat="account in accounts()"]', account4Name); - - // Validate two account balances - util.shouldContainCSS('[ng-repeat="account in accounts()"]', account1TransValue); - util.shouldContainCSS('[ng-repeat="account in accounts()"]', account3TransValue); - - }); - - it('should show account balance and transaction history when selecting account name', function() { - - // Open Transactions drawer in left navigation - browser.findElement(by.css('[translate="MY_TRANSACTIONS"]')).click(); - - // Click on 4th account and validate value and date - browser.element.all(by.repeater('account in accounts()')).get(3).click(); - util.shouldContainCSS(account3TransLocationPage, account3TransValue); - util.shouldContainCSS('date', account3TransDate); - - }); - - it('should show transaction details when clicking on date/time of transaction', function() { - - // Open Transactions drawer in left navigation - browser.findElement(by.css('[translate="MY_TRANSACTIONS"]')).click(); - - // Click on 4th account and validate value and date - browser.element.all(by.repeater('account in accounts()')).get(3).click() - browser.sleep(500); // Resolves test from failing intermittently - util.shouldContainCSS(account3TransLocationPage, account3TransValue); - browser.findElement(by.cssContainingText('date', account3TransDate)).click(); - - // Validate transaction details page - browser.sleep(500); // Required for next steps to pass in Chrome - browser.findElement(by.css('[translate="BACK_TO_FEED"]')); - browser.findElement(by.css('[translate="TRANSACTION_DETAILS"]')); - browser.findElement(by.css('[translate="FROM"]')); - browser.findElement(by.css('[translate="TO"]')); - browser.findElement(by.css('[translate="NOTES"]')); - browser.findElement(by.css('[translate="TRANSACTION_COMPLETE"]')); - browser.findElement(by.css('[translate="VALUE_AT_SEND"]')); - util.shouldContainCSS('[btc="transaction.result"]', '$0.10'); - browser.findElement(by.css('[translate="VALUE_NOW"]')); - browser.findElement(by.css('[translate="VERIFY_ON_BCI"]')); - - }); - - it('should return to account balance and transaction history from transaction details', function() { - - // Open Transactions drawer in left navigation - browser.findElement(by.css('[translate="MY_TRANSACTIONS"]')).click(); - - // Click on 4th account in list and view transaction details - browser.element.all(by.repeater('account in accounts()')).get(3).click(); - browser.element.all(by.repeater("transaction in transactions | filter:transactionFilter | orderBy:'-txTime'")).get(0).click(); - - // Return to account details and validate Request/Send buttons - browser.findElement(by.css('h4.back')).click(); - browser.findElement(by.css('[translate="REQUEST"]')); - browser.findElement(by.css('[translate="SEND"]')); - - }); - - it('should show transacted bitcoin address for each listed transaction', function() { - - // Open Transactions drawer in left navigation - browser.findElement(by.css('[translate="MY_TRANSACTIONS"]')).click(); - - // Click on account 'DONT EDIT 1' and validate transaction date - browser.element.all(by.repeater('account in accounts()')).get(1).click(); - browser.sleep(500); - util.shouldContainCSS('date.ng-binding', account1TransDate); - - // Validate address labels within account details - util.shouldContainCSS('.ng-binding', '1FV6M8WSJLRKECvGzXwVZyfRgdVHxZmjrX'); - util.shouldContainCSS('.ng-binding', '1DPrsgPctXtgN1GQcshPJhwEYr7xykWpVJ'); - util.shouldContainCSS('.ng-binding', '1H5Me956D9N39tbq8hFBJiBc6dJbRaAmxe'); - - }); - - it('should auto populate account name on Send/Receive modal', function() { - - // Open Transactions drawer in left navigation - browser.findElement(by.css('[translate="MY_TRANSACTIONS"]')).click(); - - // Click on account 'DONT EDIT 1' and validate transaction date - browser.element.all(by.repeater('account in accounts()')).get(1).click(); - browser.findElement(by.css('[translate="SEND"]')).click(); - - // Validate Send modal details - browser.findElement(by.css('[translate="FROM:"]')); - util.shouldContainCSS('span.ng-binding.ng-scope', account1TransValueTx); - - // Close Send modal, wait for and click Request button - browser.findElement(by.css('[ng-click="close()"]')).click(); - expect(element(by.css('[ng-click="request()"]')).isPresent()).toBe(true); - browser.findElement(by.css('[translate="REQUEST"]')).click(); - - // Validate Receive modal details - browser.findElement(by.css('[translate="RECEIVE_TO"]')); - util.shouldContainCSS('span.ng-binding.ng-scope', account1Name); - browser.findElement(by.css('[ng-click="close()"]')).click(); - - }); - - it('should filter by transaction type', function() { - - util.navigateTo("MY_TRANSACTIONS"); - element.all(by.css('.filter-bar')).all(by.css('[translate="SENT"]')).click(); - browser.sleep(1000); - expect(element.all(by.css('.transaction-feed')).all(by.css('[translate="RECEIVED_BITCOIN_FROM"]')).count()).toEqual(0); - - - element.all(by.css('.filter-bar')).all(by.css('[translate="RECEIVED_BITCOIN_FROM"]')).click(); - expect(element.all(by.css('.transaction-feed')).all(by.css('[translate="MOVE_BITCOIN_TO"]')).count()).toEqual(0); - - }); - -}); diff --git a/e2e-tests/util.js b/e2e-tests/util.js deleted file mode 100644 index bb76ad4403..0000000000 --- a/e2e-tests/util.js +++ /dev/null @@ -1,115 +0,0 @@ -var login = require('./ignore.js'); - -module.exports = { - - getURL: function() { - - // Visit URL and validate page title - // Use ONE of the following two lines to enable tests on staging versus localhost. - browser.driver.get('https://dev.blockchain.info/#/login'); // Dev server - //browser.driver.get('https://staging.blockchain.info/#/login'); // Staging server - //browser.driver.get('https://alpha.blockchain.info/#/login'); // Alpha server - //browser.driver.get('http://local.blockchain.com:8080/#/login'); // Localhost - - expect(browser.getTitle()).toEqual('Blockchain Wallet HD'); - - }, - - logIn: function() { - - // Clear login text fields - element(by.model('uid')).clear(); - element(by.model('password')).clear(); - - // Fill text fields with login credentials, submit - element(by.model('uid')).sendKeys(login.uid); - element(by.model('password')).sendKeys(login.pw); - element(by.id('login')).click(); - - }, - - logOut: function () { - - // Open Profile drop down menu, click Logout, and dismiss alert - element.all(by.css('[ng-click="logout()"]')).first().click(); - browser.sleep(200); - browser.switchTo().alert().accept(); - - }, - - validateHome: function () { - - // Validate account homepage details - browser.sleep(3000); // Required wait for Request and Send button validation - browser.findElement(by.css('[translate="REQUEST"]')); - browser.findElement(by.css('[translate="SEND"]')); - browser.findElement(by.css('.bc-well')); - - }, - - navigateTo: function(section) { - browser.sleep(2000); - browser.findElement(by.css('[translate="' + section + '"]')).click(); - }, - - submitBetaKey: function () { - - var promise = browser.getCurrentUrl(); - promise.then(function(currentURL) { - - // Is this still valid for localhost? - if (currentURL == 'http://local.blockchain.com:8080/#/login') { - console.log('URL is local.blockchain.com:8080'); - - // Click Create Wallet button - browser.findElement(by.css('[translate="CREATE_WALLET"]')).click(); - - } - - else if (currentURL == 'https://dev.blockchain.info/#/login') { - console.log('URL is dev.blockchain.info'); - - // Click invite key link - browser.findElement(by.css('[ng-click="prepareRegister()"]')).click(); - - // Enter beta key and click Create Wallet button - element(by.css('[ng-model="key"]')).sendKeys(login.betaKey); - browser.findElement(by.css('[ng-click="register()"]')).click(); - - } - - else if (currentURL == 'https://alpha.blockchain.info/#/login') { - console.log('URL is dev.blockchain.info'); - - // Click invite key link - browser.findElement(by.css('[ng-click="status.enterkey = !status.enterkey"]')).click(); - - // Enter beta key and click Create Wallet button - element(by.css('[ng-model="key"]')).sendKeys(login.betaKey); - browser.findElement(by.css('[ng-click="register()"]')).click(); - - } - - else { - console.log('Unable to determine server instance') - - } - }) - - }, - - shouldContainCSS: function (selector, text) { - - browser.findElement(by.cssContainingText(selector, text)); - - }, - - scrollTo: function (selector) { - var filter = browser.findElement(by.css(selector)); - var scrollIntoView = function () { - arguments[0].scrollIntoView(); - }; - browser.executeScript(scrollIntoView, filter); - } - -} diff --git a/img/recovery.pdf b/img/recovery.pdf index 3355babd04..dc448e16a4 100644 Binary files a/img/recovery.pdf and b/img/recovery.pdf differ diff --git a/karma.conf.js b/karma.conf.js index e1e760cda9..fa3d3646b4 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -112,11 +112,6 @@ module.exports = function(karma){ browsers : ['PhantomJS'], - junitReporter : { - outputFile: 'test_out/unit.xml', - suite: 'unit' - }, - reporters: ['progress','osx', 'coverage'], coverageReporter: { diff --git a/locales/en-human.json b/locales/en-human.json index 4d66d6136e..2ff71e58fc 100644 --- a/locales/en-human.json +++ b/locales/en-human.json @@ -7,11 +7,9 @@ "LOGIN_HELP": "Login Help", "LOGIN_HELP_TEXT": "Need help accessing your wallet?", "CREATE_WALLET": "Create Wallet", - "ACCOUNTS": "Accounts", - "NEW_ACCOUNT": "Add Account +", + "NEW_ACCOUNT": "Add +", "MY_TRANSACTIONS": "Transactions", "ALL": "All", - "ACCOUNT": "Account", "SEND_FEEDBACK": "Send Feedback", "BACK": "Back", "CONTACT_SUPPORT": "Contact Support", @@ -68,7 +66,7 @@ "LANGUAGE" : "Wallet Language", "PREFERENCES" : "Preferences", "PREFERENCES_EXPLAIN" : "Customize your wallet experience.", - "MENU_ACCOUNTS_AND_ADDRESSES" : "Accounts & Addresses", + "MENU_ACCOUNTS_AND_ADDRESSES" : "Addresses", "WALLET_INFO" : "Wallet Information", "WALLET_INFO_EXPLAIN" : "Use the Wallet ID to log in via our web client or scan the code below with your iPhone or Android Blockchain Wallet App to access your wallet on your mobile.", "PAIRING_CODE" : "Mobile App Pairing Code", @@ -119,20 +117,20 @@ "CANCEL_EDIT_NOTE" : "Cancel", "WALLET_RECOVERY_PHRASE" : "Wallet Recovery Phrase", "RECOVERY_PHRASE" : "Recovery Phrase", - "RECOVERY_PHRASE_EXPLAIN" : "Your recovery phrase can be used to restore all your funds in the case of a lost password or a loss of service at Blockchain. Note, that the recovery phrase never changes and recovers all of your existing bitcoins as well as newly received funds in this wallet. Please note that imported addresses are not backed up by the wallet recovery phrase. We strongly recommend to transfer funds from imported addresses into an account in this wallet.", + "RECOVERY_PHRASE_EXPLAIN" : "Your recovery phrase can be used to restore all your funds in the case of a lost password or a loss of service at Blockchain. Note, that the recovery phrase never changes and recovers all of your existing bitcoins as well as newly received funds in this wallet. Please note that imported addresses are not backed up by the wallet recovery phrase. We strongly recommend to transfer funds from imported addresses into this wallet.", "CONFIRM_RECOVERY_PHRASE" : "Backup Phrase", "RECOVERY_ERROR": "Unable to import now, please try again.", "NEXT_STEP" : "Next Step", "NEXT" : "Next", "EMAIL" : "Email", "NEW_ACCT_WELCOME" : "Create A New Blockchain Wallet", - "CREATE_NEW_ACCOUNT" : "Create New Account", - "CREATE_ACCOUNT" : "Create Account", + "CREATE_NEW_ACCOUNT" : "Create New", + "CREATE_ACCOUNT" : "Create New", "CREATE_ADDRESS" : "New Address", - "ACCOUNT_NAME" : "Account Name:", + "ACCOUNT_NAME" : "Name:", "REVEAL_XPUB" : "Show xPub", "MANAGE_ADDRESSES" : "Manage Addresses", - "ACCOUNT_XPUB_MODAL_TITLE" : "Account xPub", + "ACCOUNT_XPUB_MODAL_TITLE" : "Extended Public Key", "PENDING" : "Pending", "COMPLETE" : "Complete", "TRANSACTION_COMPLETE" : "Transaction Complete", @@ -143,11 +141,8 @@ "FROM" : "From", "FROM:" : "From:", "TO" : "To", - "TO:" : "To:", "FEE" : "Fee", "TOTAL" : "Total", - "TO_ACCOUNT" : "To Account", - "INTERNAL" : "Internal", "GOOGLE_AUTH_CODE" : "Google Authenticator", "SMS_CODE" : "SMS Code", "EMAIL_CODE" : "Email Code", @@ -176,8 +171,7 @@ "HANDLE_BITCOIN_LINKS_EXPLAIN" : "Enable this to allow your Blockchain Wallet to handle bitcoin payment links in the web browser. This will make your experience more convenient when transacting online.", "SET_HANDLE_BITCOIN_LINKS" : "Enable", "HANDLE_BITCOIN_LINKS_STATUS_UNKNOWN" : "We can't detect whether or not handling of bitcoin links has been enabled. If it has already been enabled, nothing will happen.", - "ACCOUNT_MANAGEMENT" : "Account Management", - "ACCOUNT_MANAGEMENT_EXPLAIN" : "Your Blockchain Wallet can create separate accounts within your single wallet. You may find this feature useful when you would like to separate your funds between a savings account and a spending account, or between a personal account and a business account.", + "ACCOUNT_MANAGEMENT_EXPLAIN" : "You can customize the way you organize your bitcoins in your Blockchain Wallet. You may find this useful when you would like to separate your funds between personal and business expenses, or put your savings aside.", "BALANCE" : "Balance", "MAKE_DEFAULT" : "Make default", "RENAME" : "Rename", @@ -208,7 +202,7 @@ "DISABLE_BLOCK_TOR" : "Allow", "ALLOWED" : "Allowed", "BLOCKED" : "Blocked", - "RENAME_ACCOUNT" : "Rename Account", + "RENAME_ACCOUNT" : "Name", "RENAME" : "Rename", "TRANSACTION_WILL_COMPLETE_IN" : "This transaction will complete in approximately {{ minutes }} minutes.", "SPENDABLE_ADDRESSES" : "Spendable Addresses", @@ -222,13 +216,12 @@ "SHOW_PAIRING_CODE" : "Show Pairing Code", "PAIRING_CODE_EXPLAIN" : "Scan the code below with your iPhone or Android Blockchain Wallet App to quickly and easily connect to your wallet. Note that this only works with the V3 beta iPhone or Android app, not with the current store version.", "MOBILE" : "Mobile", - "INSUFFICIENT" : "Insufficient", "WATCH_ONLY" : "Watch Only", "WARNING" : "Warning", "IMPORT_BITCOIN_ADDRESS" : "Import Existing Bitcoin Address", "IMPORT_BITCOIN_ADDRESS_SWEEP" : "Transfer Funds from Imported Address", "IMPORT_BITCOIN_ADDRESS_EXPLAIN" : "Your wallet automatically creates new bitcoin addresses as it needs them. You can optionally import an existing address and transfer the funds to your wallet if you have the corresponding Private Key. This is an advanced functionality and only suggested for advanced users.", - "CONFIRM_NOT_SWEEP" : "Are you sure? We recommend that you sweep these funds into an account.", + "CONFIRM_NOT_SWEEP" : "Are you sure? We recommend that you sweep these funds.", "YOUR_PRIVATE_KEY" : "Show Private Key", "PRIVATE_KEY_VALID" : "The Private Key appears to be Valid", "ADDRESS_VALID" : "The Bitcoin address is valid", @@ -334,7 +327,6 @@ "BITCOIN_CURRENCY_EXPLAIN": "Adjust the precision you would prefer bitcoin values to be displayed in.", "JUST_RECEIVED_BITCOIN" : "You've just received Bitcoin!", "SUCCESS" : "Success!", - "ACCOUNT_CREATED" : "You've successfully created an account", "BITCOIN_SENT" : "You've successfully sent bitcoin", "NO_TRANSACTIONS_YET" : "Your Transactions", "SORRY_ZERO_TXS" : "Sorry, we couldn't find any transactions!", @@ -352,7 +344,6 @@ "RESET_FORM" : "Reset Form", "REGULAR_SEND" : "Regular Send", "MINERS_FEE" : "Miner's Fee", - "MINERS_FEE_EXPLAIN" : "Select from a predetermined list or enter a custom amount", "TOTAL_AVAILABLE": "Total available", "GO_BACK" : "Go Back", "CLICK_HERE_TO_CREATE_WALLET" : "create a wallet", @@ -413,12 +404,12 @@ "NOT_STORED" : "Not Stored", "PHRASE_BACKED" : "Phrase Backed", "EMAIL_VERIFIED" : "Email Verified", - "LAUNCHED" : "We've launched!", - "GET_STARTED" : "Get Started", - "WELCOME_TEXT" : "We've launched our wallet! And we're excited to show you all the great features we've added. A brand new Security Center, account management, revamped algorithms and a shiny new interface to name a few - go ahead, get started!", + "FIRST_LOGIN_TITLE" : "Welcome!", + "FIRST_LOGIN_TEXT" : "Welcome to the new version of the Blockchain Wallet! A brand new Security Center, a Recovery phrase to back up your wallet, no address reuse for increased privacy, and a completely new user interface, just to name a few. ", + "FIRST_LOGIN_ACTION" : "Get Started", "TOS" : "ToS", "PRIVACY" : "Privacy Policy", - "CREATE_NEW_ACCOUNT_MODAL" : "Divide your wallet into multiple accounts to better allocate, track and manage your funds. Some common account names include Savings, Spending, and Business Expenses.", + "CREATE_NEW_ACCOUNT_MODAL" : "Divide your wallet to better allocate, track and manage your bitcoins. Some common names include Savings, Spending, and Business Expenses.", "TRANSACTION_DETAIL_STATUS" : "Your transaction has been submitted to the bitcoin network and is waiting for miners to validate the transaction. A transaction is considered to be confirmed when there are 3 network confirmations.", "SEND_BITOIN_STEP2B" : "Add a note to remind yourself of what this transaction relates to. This note will be private and only seen by you, unless you select the Make Public option. If a note is public, anyone viewing the transaction on Blockchain will be able to read your note.", "ADVANCED_SEND_STEP_1" : "A normal miners fee is recommended for most transactions. You can override this recommendation but be advised that lower fees may take several days to confirm.", @@ -427,7 +418,7 @@ "VALUE_NOW" : "Value Now", "VERIFY_ON_BCI" : "Verify on Blockchain.info", "ARE_YOU_SURE" : "We just want to double check! Are you sure you wish to proceed? Remember you cannot go back to your old wallet :)", - "ACCOUNT_NAME_TAKEN" : "That account name is already in use", + "ACCOUNT_NAME_TAKEN" : "That name is already in use", "EMAIL_ADDRESS_TOOLTIP" : "Your verified email address is used to send login codes when unusual activity is detected and to send bitcoin payment alerts when you receive funds.", "RECOVERY_PHRASE_TOOLTIP" : "Your recovery phrase allows you to restore your wallet in case of loss of password or extended downtime on our servers.", "PASSWORD_HINT_TOOLTIP" : "Allows us to send your password hint to your verified email address in case you forget your password.", @@ -453,7 +444,7 @@ "DYK_RECOVERY_TITLE": "Passwords are not stored or shared with us", "DYK_TX_FEES": "Transaction fees are used for sending bitcoins, which are collected by the Bitcoin network of miners. To assure your transaction is confirmed by the network, we automatically include the appropriate fee based on the network standards.", "DYK_TX_FEES_TITLE": "What are transaction fees for?", - "DYK_FEEDBACK": "Our Alpha Wallet depends on you! Send us what you liked, loved, or hated to our team. All of the feedback is collected and reviewed directly by our development team.", + "DYK_FEEDBACK": "Our Beta Wallet depends on you! Send us what you liked, loved, or hated to our team. All of the feedback is collected and reviewed directly by our development team.", "DYK_FEEDBACK_TITLE": "We're interested in your feedback", "DYK_BTC_VALUE": "Our wallet balances are reflected in bitcoin, but we do show a dollar estimation based on the current market price. The amount of bitcoins will stay the same regardless of market fluctuations in price.", "DYK_BTC_VALUE_TITLE": "Bitcoin's value is constantly changing", @@ -462,7 +453,7 @@ "LOGGING" : "Activity Logging", "LOG" : "Log", "LOGGING_EXPLAIN" : "Record wallet activity and display it in your activity feed.", - "ACTIVITY_1": "Accounts", + "ACTIVITY_1": "Addresses", "ACTIVITY_1_DESC": "Created My Bitcoin Wallet", "ACTIVITY_2_DESC": "Set password", "ACTIVITY_3_DESC": "Created wallet!", @@ -474,20 +465,39 @@ "SIGNOUT": "Sign out", "EDIT_NOTE": "Edit", "DELETE_NOTE": "Delete", - "ALPHA_WARNING": "Please note, this is an Alpha of a new product. Please do not test this wallet with more funds than you are willing to lose.", - "XPUB_WARNING": "You should only give this Public Key to those you trust. With this information, they may be able to keep track of your payments, and may be able to disrupt your access to your wallet.", + "USER_AGREEMENT" : "Beta Program Participation Agreement", + "USER_AGREEMENT_WARNING": "Please note, this is an Beta of a new product. Please do not test this wallet with more funds than you are willing to lose.", + "XPUB_WARNING": "You should only give this Extended Public Key (xPub) to those you trust. With this information, they may be able to keep track of your payments, and may be able to disrupt your access to your wallet.", "LOST_WALLET_ID": "I've lost my Wallet ID", "LOST_WALLET_PWD": "I've lost my Wallet Password", "LOST_2FA": "I've lost my 2FA Device", "LOST_ID_DESC": "Email me a reminder with my Wallet ID to my verified email address", "LOST_PWD_DESC": "Recover your funds with your 12 word recovery passphrase", - "LOST_2FA_DESC": "Contact support to help regain access to your wallet", + "RESET_2FA" : "Reset 2FA", "REMIND_ME": "Remind Me", "REMIND_ME_HELP": "Lost your Wallet Identifier? We'll send it to you via your verified email.", "REMIND_SUCCESS": "You will receive an email containing your Wallet Identifier soon.", "CAPTCHA": "Captcha", "CAPTCHA_EXPAIN": "So that we know you're not a robot", "CAPTCHA_INCORRECT": "The captcha you provided was incorrect, please try again", + "LOST_2FA_DESC": "Reset two step verification to regain access to your wallet", + "RESET_2FA_HELP_1" : "Are you unable to gain access to your wallet because you lost your two factor authentication (2FA) device or are unable to access your email account?", + "RESET_2FA_HELP_2" : "Please submit the form below and check your email for further instructions. 2FA reset requests are automatically approved after a certain time. The process will be quicker with more precise details provided to us. Your IP address and browser information will be recorded on submission.", + "RESET_2FA_WALLET_IDENTIFIER" : "Wallet Identifier", + "RESET_2FA_WALLET_IDENTIFIER_EXPLAIN" : "If you forgot your wallet identifier (36 characters long containing 4 dashes), please", + "RESET_2FA_REGISTERED_EMAIL" : "Registered Email", + "RESET_2FA_REGISTERED_EMAIL_EXPLAIN" : "Enter the email associated with your wallet. If you lost access to this email, please enter it regardless.", + "RESET_2FA_NEW_EMAIL" : "New Email", + "RESET_2FA_NEW_EMAIL_EXPLAIN" : "If you lost access to the email associated with your wallet, enter a new email. If the 2FA reset request is approved, this email will automatically be set as your new wallet email.", + "RESET_2FA_SECRET_PHRASE" : "Secret Phrase", + "RESET_2FA_SECRET_PHRASE_EXPLAIN" : "Enter your wallet Secret Phrase here if you have one set. If the Secret Phrase is correct, your request will be approved much quicker. If you don't know what this is, leave it blank.", + "RESET_2FA_MESSAGE" : "Message", + "RESET_2FA_MESSAGE_EXPLAIN" : "Enter a message for Blockchain.info admins to review.", + "OPTIONAL" : "Optional", + "LOOK_IT_UP_HERE" : "look it up here", + "RESET_2FA_SUCCESS" : "You will receive an email with further instructions soon.", + "RESET_2FA_CHECKING" : "Processing your request", + "NEW_EMAIL" : "Registered Email", "QUOTA_EXCEEDED": "You have reached the maximum number of allowed attempts, please try again later", "UNKNOWN_ERROR": "An unknown error occurred, please try again later", "CONTINUE_TO_LOGIN": "Continue to Login", @@ -502,6 +512,22 @@ "REDIRECTING": "We're redirecting you to your wallet now!", "TAKES_A_WHILE": "This may take a while. If your browser asks if you want to cancel the script, please press continue.", "NOTIFICATIONS" : "Email Notifications", - "NOTIFICATIONS_EXPLAIN" : "Enable notifications to receive an email whenever you receive bitcoins. Only labeled addresses in your accounts and imported addresses are eligible to trigger notifications.", - "VERIFY_EMAIL_FIRST" : "Please verify your email address first." + "NOTIFICATIONS_EXPLAIN" : "Enable notifications to receive an email whenever you receive bitcoins. Only labeled addresses and imported addresses are eligible to trigger notifications.", + "VERIFY_EMAIL_FIRST" : "Please verify your email address first.", + "UNSUBSCRIBE_SUCCESS" : "We have removed your email address (and phone number) from our systems.", + "AUTHORIZE_APPROVE_SUCCESS" : "Login approved! Please return to your previous browser / tab to see your wallet.", + "AUTHORIZE_APPROVE_OTHER_BROWSER" : "Login attempt from other browser", + "AUTHORIZE_APPROVE_OTHER_BROWSER_EXPLAIN" : "Someone, hopefully you, is attempting to login to your wallet from a different browser.", + "AUTHORIZE_APPROVE_REQUESTING_DEVICE" : "Requesting device", + "AUTHORIZE_APPROVE_THIS_DEVICE" : "This device", + "ACCEPT" : "Accept", + "REJECT" : "Reject", + "BROWSER" : "Browser", + "IP_ADDRESS" : "IP Address", + "COUNTRY_OF_ORIGIN" : "Country Of Origin", + "AUTHORIZE_REJECT_SUCCESS" : "Login attempt rejected! Please contact support if this was not you.", + "CHECKING_LOGIN_ATTEMPT" : "Checking login attempt", + "SEARCH" : "Search", + "BALANCES" : "Balances", + "MUST_SELECT_ORIGIN" :"Must select where to send from" } diff --git a/package.json b/package.json index 6a4b2df320..c6613ff0d3 100644 --- a/package.json +++ b/package.json @@ -3,23 +3,12 @@ "private": true, "version": "0.0.0", "description": "AngularJS front-end to My-Wallet-V3.", - "dependencies": { - "basic-auth": "^1.0.3", - "body-parser": "^1.14.1", - "compression": "^1.6.0", - "errorhandler": "^1.4.2", - "express": "^4.13.3", - "method-override": "^2.3.5", - "morgan": "^1.6.1", - "request": "^2.55.0" - }, + "dependencies": {}, "devDependencies": { "bower": "^1.3.1", - "chalk": "^0.5.1", "coffee-script": "^1.9.1", - "compression": "^1.3.0", "ejs": "~0.8.4", - "get-stdin": "^3.0.0", + "express": "^4.13.3", "git-changelog": "^0.1.8", "grunt": "^0.4.5", "grunt-autoprefixer": "^3.0.0", @@ -43,34 +32,21 @@ "grunt-rename-assets": "0.0.1", "grunt-shell": "^1.1.1", "grunt-surround": "^0.1.0", + "grunt-text-replace": "^0.4.0", "isparta": "^3.0.4", "istanbul": "^0.3.19", "jade": "*", "karma": "^0.12.16", "karma-babel-preprocessor": "^5.2.2", - "karma-chrome-launcher": "^0.2.0", "karma-coffee-preprocessor": "^0.2.1", "karma-coverage": "^0.5.1", "karma-jade-preprocessor": "0.0.11", "karma-jasmine": "^0.2.0", - "karma-junit-reporter": "^0.2.2", "karma-ng-jade2js-preprocessor": "^0.1.5", "karma-osx-reporter": "^0.1.0", "karma-phantomjs-launcher": "^0.1.4", - "mkdir": "0.0.2", - "mkdirp": "^0.5.0", - "mocha": "^1.21.5", - "nan": "^1.3.0", "node-env-file": "^0.1.4", - "node-watch": "^0.3.4", - "nodemon": "^1.8.1", - "object-assign": "^1.0.0", - "protractor": "~1.0.0", - "shelljs": "^0.3.0", - "sinon": "^1.10.3", - "spies": "^0.1.6", - "tmp": "0.0.23", - "yargs": "^1.3.2" + "shelljs": "^0.3.0" }, "scripts": { "postinstall": "node_modules/bower/bin/bower install", @@ -85,7 +61,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/blockchain/My-Wallet-HD-Frontend" + "url": "https://github.com/blockchain/My-Wallet-V3-Frontend" }, "keywords": [ "blockchain" diff --git a/server.js b/server.js index 1eae668bc5..d73dfadb91 100644 --- a/server.js +++ b/server.js @@ -1,48 +1,29 @@ 'use strict'; var express = require('express') - , compression = require('compression') - , bodyParser = require('body-parser') - , morgan = require('morgan') - , errorhandler = require('errorhandler') - , methodOverride = require('method-override') , ejs = require('ejs') - , auth = require('basic-auth') - , request = require('request') , path = require('path') - , fs = require('fs'); loadEnv('.env'); var port = process.env.PORT || 8080 , dist = parseInt(process.env.DIST) === 1 - , beta = parseInt(process.env.BETA) === 1 , origins = (process.env.BLOCKCHAIN || '').split(' ') , whitelist = (process.env.IP_WHITELIST || '').split(' ') - , admin = { user: 'blockchain', pass: process.env.ADMIN_PASSWORD }; + , rootURL = process.env.ROOT_URL || 'https://blockchain.info/' + , webSocketURL = process.env.WEBSOCKET_URL || false // App configuration var app = express(); -app.use(morgan('combined')); -app.use(bodyParser.json()); -app.use(compression({ - threshold: 512 -})); - -app.use(errorhandler({ - dumpExceptions: true, - showStack: true -})); - app.use(function (req, res, next) { if (req.url === '/') { var cspHeader = ([ - "img-src 'self' blockchain.info data:", + "img-src 'self' " + rootURL + " data:", "style-src 'self' 'sha256-vv5i1tRAGZ/gOQeRpI3CEWtvnCpu5FCixlD2ZPu7h84=' 'sha256-47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU='", "child-src 'none'", "script-src 'self' 'sha256-mBeSvdVuQxRa2pGoL8lzKX14b2vKgssqQoW36iRlU9g=' 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='", - "connect-src 'self' *.blockchain.info *.blockchain.com wss://*.blockchain.info https://blockchain.info https://api.sharedcoin.com", + "connect-src 'self' " + rootURL + " " + (webSocketURL || "wss://*.blockchain.info"), "object-src 'none'", "media-src 'self' data: mediastream: blob:", "font-src 'self'", '' @@ -54,8 +35,6 @@ app.use(function (req, res, next) { if (whitelist != '' && whitelist.indexOf(ip.split(', ')[0]) < 0) { console.log(ip); res.status(403).send('I\'m sorry Dave, I can\'t let you do that.'); - } else if (dist && beta) { - res.render('index-beta.html'); } else if (dist) { res.render('index.html'); } else { @@ -64,9 +43,7 @@ app.use(function (req, res, next) { return; } - if (req.url.indexOf('beta_key')) { - res.setHeader('Cache-Control', 'public, max-age=0, no-cache'); - } else if (dist) { + if (dist) { res.setHeader('Cache-Control', 'public, max-age=31557600'); } else { res.setHeader('Cache-Control', 'public, max-age=0, no-cache'); @@ -81,273 +58,13 @@ if (dist) { app.set('views', path.join(__dirname, 'dist')); } else { console.log('Development mode: multiple javascript files, not cached'); - app.use(methodOverride()); app.use(express.static(__dirname)); app.set('view engine', 'jade'); app.set('views', __dirname); } -// Routing -if (beta) { - // Beta system enabled - console.log('Enabling beta invite system'); - var v3Beta = require('my-wallet-v3-beta-module')(path.join(__dirname, process.env.BETA_DATABASE_PATH || '')); - - app.get(/^\/key-.{8}$/, function (req, res) { - var key = req.path.split(path.sep)[1].split('-')[1]; - v3Beta.emailLinkFollowed({key:key}); - res.cookie('key', '"' + key + '"'); - res.redirect('/'); - }); - - // *.blockchain.info/logo-key-{key} redirects to image on Amazon - app.get(/^\/key-logo-.{8}$/, function (req, res) { - var key = req.path.split(path.sep)[1].split('-')[2]; - v3Beta.emailOpened({key:key}); - res.redirect('https://s3.amazonaws.com/blockchainwallet/bc-logo-family.png'); - }); - - app.post('/check_beta', function (req, res) { - v3Beta.verifyLimit(function (err, open) { - if (err) res.json({ open: false, error: {message: err} }); - else res.json({ open: open }); - }); - }); - - app.post('/verify_wallet_created', function (req, res) { - v3Beta.verifyLimit(function (err, open) { - if (err) res.json({ success: false, error: {message: err} }); - else res.json({ success: open }); - }); - }); - - app.post('/check_guid_for_beta_key', function (req, res) { - v3Beta.isGuidAssociatedWithBetaKey(req.body.guid, function (err, verified) { - if (err) { - res.json({ - verified: false, - error: { - message: 'There was a problem verifying your access permission. Please try again later.', - err: err - } - }); - } else if (verified) { - res.json({ verified: true }); - } else { - res.json({ - verified: false, - error: { message: 'Please create a new Alpha wallet first.' } - }); - } - }); - }); - - app.post('/register_guid', function (req, res) { - v3Beta.verifyLimit(function (err, open) { - if (err) { - res.json({ success: false, error: {message: err} }); - } else { - v3Beta.assignKey('', req.body.email, req.body.guid, function (key) { - v3Beta.newWalletCreated(key, function () { - res.json({ success: true }); - }); - }); - } - }); - }); - - app.post('/whitelist_guid', function (req, res) { - if (req.body == null) { - res.json({ error: 'no request body' }); - } else if (req.body.secret !== process.env.WHITELIST_SECRET) { - res.json({ error: 'incorrect secret' }); - } else if (req.body.guid == null) { - res.json({ error: 'missing request body guid parameter' }); - } else { - var name = req.body.name || 'Mobile Tester'; - v3Beta.assignKey(name, req.body.email, req.body.guid, function (err, key) { - res.json({ - error: err, - key: key - }); - }); - } - }); - - app.get('/admin/?', authenticate(admin), function (req, res) { - var adminIndex = dist ? 'admin.html' : 'app/admin.jade'; - res.render(adminIndex); - }); - - app.get("/admin/api/:method", authenticate(admin), function (req, res) { - switch (req.params.method) { - case 'get-all-keys': - v3Beta.getKeys(function (err, data) { - res.send(JSON.stringify(data)); - }); - break; - case 'get-sorted-keys': - v3Beta.getKeys(req.query, function (err, data) { - res.json({ - error: err, - data: data - }); - }); - break; - case 'assign-key': - v3Beta.assignKey(req.query.name, req.query.email, req.query.guid, function (err, key) { - res.json({ - error: err, - key: key - }); - }); - break; - case 'delete-key': - v3Beta.deleteKey(req.query, function (err) { - res.json({ error: err }); - }); - break; - case 'update-key': - v3Beta.updateKey(req.query.selection, req.query.update, function (err) { - res.json({ error: err }); - }); - break; - case 'activate-key': - v3Beta.activateKey(req.query.selection, req.query.update, function (err, data) { - res.json({ - error: err, - data: data - }); - }); - break; - case 'remind-email': - v3Beta.remindEmail(req.query.key, function (err, data) { - res.json({ - error: err, - data: data - }); - }); - break; - case 'activate-all': - var range = [req.query.min || 0, req.query.max || 100000]; - v3Beta.activateAll(range, function (err, data) { - res.json({ - error: err, - data: data - }); - }); - break; - case 'remind-all': - var range = [req.query.min || 0, req.query.max || 100000]; - v3Beta.remindAll(range, function (err, data) { - res.json({ - error: err, - data: data - }); - }); - break; - case 'resend-activation': - v3Beta.resendActivationEmail(req.query.key, function (err, data) { - res.json({ - error: err, - data: data - }); - }); - break; - case 'resend-many': - var range = [req.query.min || 0, req.query.max || 100000]; - v3Beta.resendMany(range, function (err, data) { - res.json({ - error: err, - data: data - }); - }); - break; - case 'wallets-created': - v3Beta.fetchNumWalletsCreated(function (err, count) { - res.json({ - error: err, - count: count - }); - }); - break; - case 'get-csv': - v3Beta.fetchCSV({}, function (err, csv) { - fs.writeFileSync('tmp.csv', csv); - res.download('tmp.csv', 'emails.csv', function () { - fs.unlink('tmp.csv'); - }); - }); - break; - case 'set-percent-requested': - var percent = parseInt(req.query.percent) - , isNumber = !isNaN(percent); - if (isNumber) process.env.PERCENT_REQUESTED = percent; - res.json({ - success: Boolean(isNumber) - }); - break; - default: - res.status(400).json({ - error: { message: 'Unknown request method' }, - success: false - }); - } - }); -} - -app.get('/verify-email', function (req, res) { - request.get('https://blockchain.info/wallet' + req.originalUrl); - res.cookie('email-verified', true); - res.redirect('/'); -}); - -app.get('/authorize-approve', function (req, res) { - var approveHTML = '\ - \n\ - \n\ - \n\ - \n\ - Verifying authorization request\n\ - \n\ - \n\ - '; - res.send(approveHTML); -}); - -app.post('/feedback', function (req, res) { - var jira = 'https://blockchain.atlassian.net/rest/collectors/1.0/template/feedback/e6ce4d72' - , headers = { 'X-Atlassian-Token': 'nocheck' }; - request.post({ - url: jira, - headers: headers, - form: req.body - }, function (err, httpResponse, body) { - res.json({ success: !(err != null) }); - }); -}); - -app.get('/unsubscribe', function (req, res) { - request.get('https://blockchain.info/wallet' + req.originalUrl); - res.redirect('/'); -}); - -app.get(/^\/.{8}-.{4}-.{4}-.{4}-.{12}$/, function (req, res) { - res.cookie('uid', '"' + req.path.split(path.sep)[1] + '"'); - res.redirect('/'); -}); - app.use(function (req, res) { - res.send('

404 Not Found

'); + res.send(404,'

404 Not Found

'); }); app.listen(port, function () { @@ -366,20 +83,6 @@ function allowOrigins(origins) { }; } -function authenticate(options) { - return function (req, res, next) { - var credentials = auth(req) - , authorized = credentials && - credentials.name === options.user && - credentials.pass === options.pass; - authorized ? next() : issueChallenge(); - function issueChallenge() { - res.setHeader('WWW-Authenticate', 'Basic realm="blockchain-wallet-v3"'); - res.sendStatus(401); - } - }; -} - // Helper functions function loadEnv(envFile) { try { diff --git a/tests/controllers/account_form_ctrl_spec.coffee b/tests/controllers/account_form_ctrl_spec.coffee index f3685fa29c..e7ec18f3ad 100644 --- a/tests/controllers/account_form_ctrl_spec.coffee +++ b/tests/controllers/account_form_ctrl_spec.coffee @@ -76,13 +76,6 @@ describe "AccountFormCtrl", -> expect(Wallet.accounts()[Wallet.accounts().length - 1].label).toBe("New Account") ) - it "should show a confirmation modal", inject(($uibModal)-> - spyOn($uibModal, "open").and.callThrough() - scope.createAccount() - expect($uibModal.open).toHaveBeenCalled() - expect($uibModal.open.calls.argsFor(0)[0].windowClass).toEqual("notification-modal") - ) - describe "rename", -> it "original name should be shown", -> diff --git a/tests/controllers/accounts_ctrl_spec.coffee b/tests/controllers/accounts_ctrl_spec.coffee index e19defff9a..993e5bd4a4 100644 --- a/tests/controllers/accounts_ctrl_spec.coffee +++ b/tests/controllers/accounts_ctrl_spec.coffee @@ -63,12 +63,6 @@ describe "WalletNavigationCtrl", -> expect(scope.showOrHide()).toBe(false) ) - it "should open modal to see Terms of Service", inject(() -> - spyOn(modal, "open") - scope.termsOfService() - expect(modal.open).toHaveBeenCalled() - ) - it "should open modal to see Privacy Policy", inject(() -> spyOn(modal, "open") scope.privacyPolicy() diff --git a/tests/controllers/app_ctrl_spec.coffee b/tests/controllers/app_ctrl_spec.coffee index c2b44ed472..1477dca8f4 100644 --- a/tests/controllers/app_ctrl_spec.coffee +++ b/tests/controllers/app_ctrl_spec.coffee @@ -37,7 +37,7 @@ describe "AppCtrl", -> expect(scope.status.isLoggedIn).toBe(false) spyOn($state, "go") scope.$broadcast("$stateChangeSuccess", {name: "home"}) - expect($state.go).toHaveBeenCalledWith("public.login") + expect($state.go).toHaveBeenCalledWith("public.login-no-uid") ) it "should not redirect to login if logged in", inject((Wallet, $state) -> diff --git a/tests/controllers/claim_ctrl_spec.coffee b/tests/controllers/claim_ctrl_spec.coffee index 6c67e30fe2..a2f3231dd3 100644 --- a/tests/controllers/claim_ctrl_spec.coffee +++ b/tests/controllers/claim_ctrl_spec.coffee @@ -23,7 +23,7 @@ describe "ClaimCtrl", -> return it "should redirect to login", inject(($state) -> - expect($state.go).toHaveBeenCalledWith("public.login") + expect($state.go).toHaveBeenCalledWith("public.login-no-uid") ) it "should store the goal", inject((Wallet) -> diff --git a/tests/controllers/send_ctrl_spec.coffee b/tests/controllers/send_ctrl_spec.coffee index cc43c98485..da08354e0f 100644 --- a/tests/controllers/send_ctrl_spec.coffee +++ b/tests/controllers/send_ctrl_spec.coffee @@ -517,7 +517,7 @@ describe "SendCtrl", -> it "should set the label to an account", -> scope.transaction.destinations[0] = scope.accounts()[0] scope.updateToLabel() - expect(scope.toLabel).toEqual('Checking Account') + expect(scope.toLabel).toEqual('Checking') it "should set the label when advanced", -> scope.advanced = true diff --git a/tests/controllers/signup_controller_spec.coffee b/tests/controllers/signup_controller_spec.coffee index 572777ea27..fe28c0d63b 100644 --- a/tests/controllers/signup_controller_spec.coffee +++ b/tests/controllers/signup_controller_spec.coffee @@ -7,17 +7,16 @@ describe "SignupCtrl", -> beforeEach angular.mock.module("walletApp") beforeEach -> - angular.mock.inject ($injector, $rootScope, $controller) -> + angular.mock.inject ($injector, $rootScope, $controller, $compile) -> Wallet = $injector.get("Wallet") - MyWallet = $injector.get("MyWallet") - - MyWallet.createNewWallet = (email, pass, translation, lang, currency, success, error) -> success() - MyWallet.isValidateBIP39Mnemonic = () -> true Wallet.login = (uid, pass, code, twoFactor, success, error) -> success() + Wallet.create = (password, email, currency, language, success) -> success("new_guid") + Wallet.settings_api = + change_language: (code, success) -> success() + change_local_currency: () -> + Wallet.changeCurrency = () -> - Wallet.settings_api.change_language = (-> ) - Wallet.settings_api.change_local_currency = (-> ) scope = $rootScope.$new() @@ -26,13 +25,18 @@ describe "SignupCtrl", -> $stateParams: {}, $uibModalInstance: modalInstance - scope.isValid = [false, false] - scope.fields.email = "a@b.com" - scope.fields.password = "testing" - scope.fields.confirmation = "testing" - scope.fields.acceptedAgreement = true - scope.form = {$error: {email: null}} - scope.validate() + element = angular.element( + '
' + + '' + + '' + + '' + + '' + + '
' + ) + scope.model = { fields: { email: '', password: '', confirmation: '', acceptedAgreement: false } } + $compile(element)(scope) + + scope.$digest() return @@ -44,101 +48,132 @@ describe "SignupCtrl", -> expect(Alerts.clear).toHaveBeenCalled() ) - describe "first step", -> - it "should be step 1", -> - expect(scope.currentStep).toBe(1) - - it "should go to second step", -> - scope.nextStep() - expect(scope.currentStep).toBe(2) - - it "should not display an error if password is still empty", -> - scope.fields.currentPassword = "test" - scope.fields.password = "" - scope.validate() - expect(scope.isValid[0]).toBe(false) - expect(scope.errors.password).toBeNull() - - # it "should display an error if password is too short", -> - # scope.fields.currentPassword = "test" - # scope.fields.password = "1" - # scope.validate() - # expect(scope.isValid[0]).toBe(false) - # expect(scope.errors.password).not.toBeNull() - - it "should not display an error if password confirmation is still empty", -> - scope.fields.currentPassword = "test" - scope.fields.password = "testing" - scope.fields.confirmation = "" + it "should have initial values", -> + expect(scope.fields.email).toBeDefined() + expect(scope.fields.password).toBeDefined() + expect(scope.fields.confirmation).toBeDefined() + expect(scope.fields.acceptedAgreement).toBe(false) - scope.validate() + it "should not register when invalid", -> + spyOn(scope, 'createWallet') + scope.signupForm.password.$setViewValue('') + scope.$digest() - expect(scope.isValid[0]).toBe(false) - expect(scope.errors.confirmation).toBeNull() + scope.signup() + expect(scope.createWallet).not.toHaveBeenCalled() - it "should not display an error if password confirmation matches", -> - scope.fields.currentPassword = "test" - scope.fields.password = "testing" - scope.fields.confirmation = "testing" - - scope.validate() + describe "password", -> - expect(scope.isValid[0]).toBe(true) - expect(scope.errors.confirmation).toBeNull() + beforeEach -> + form = scope.signupForm + form.email.$setViewValue('a@b.com') + form.agreement.$setViewValue(true) + scope.$digest() - it "should display an error if password confirmation does not match", -> - scope.fields.currentPassword = "test" - scope.fields.password = "testing" - scope.fields.confirmation = "wrong" + it "should not have an error if password confirmation matches", -> + scope.signupForm.password.$setViewValue('testing') + scope.signupForm.confirmation.$setViewValue('testing') + scope.$digest() + expect(scope.signupForm.confirmation.$valid).toBe(true) - scope.validate() + it "should have an error if password confirmation does not match", -> + scope.signupForm.password.$setViewValue('testing') + scope.signupForm.confirmation.$setViewValue('wrong') + scope.$digest() + expect(scope.signupForm.confirmation.$valid).toBe(false) - expect(scope.isValid[0]).toBe(false) - expect(scope.errors.confirmation).not.toBeNull() + describe "agreement", -> - it "should not go to second step is invalid", -> - scope.fields.password = "" # invalid - scope.nextStep() - expect(scope.currentStep).toBe(1) + beforeEach -> + form = scope.signupForm + form.email.$setViewValue('a@b.com') + form.password.$setViewValue('my_password12345') + form.confirmation.$setViewValue('my_password12345') + scope.$digest() - describe "wallet creation", -> + it "should not be signed by default", -> + expect(scope.fields.acceptedAgreement).toBe(false) - it "should create a new wallet", (done) -> - inject((MyWallet) -> - spyOn(MyWallet, 'createNewWallet') - scope.createWallet (-> ) - expect(MyWallet.createNewWallet).toHaveBeenCalled() - done() - ) + it "should be signed by the user to register", -> + expect(scope.signupForm.$valid).toBe(false) + scope.signupForm.agreement.$setViewValue(true) + scope.$digest() + expect(scope.signupForm.$valid).toBe(true) - it "should add uid to cookieStore", (done) -> - inject(($cookieStore) -> - spyOn($cookieStore, 'put') - scope.createWallet (uid) -> - expect($cookieStore.put).toHaveBeenCalledWith('uid', uid) - done() - ) + describe "signup()", -> - describe "second step", -> beforeEach -> - scope.currentStep = 2 + form = scope.signupForm + form.email.$setViewValue('a@b.com') + form.password.$setViewValue('my_password12345') + form.confirmation.$setViewValue('my_password12345') + form.agreement.$setViewValue(true) + scope.$digest() + + it "should call createWallet()", -> + spyOn(scope, "createWallet") + scope.signup() + expect(scope.createWallet).toHaveBeenCalled() - it "should have a list of languages", -> - expect(scope.languages.length).toBeGreaterThan(1) + it "should not call createWallet() if validation failed", -> + spyOn(scope, "createWallet") - it "should have a list of currencies", -> - expect(scope.currencies.length).toBeGreaterThan(1) + scope.signupForm.password.$setViewValue('weak') + scope.$digest() + + scope.signup() + expect(scope.createWallet).not.toHaveBeenCalled() + it "should create a new wallet", inject((Wallet) -> + spyOn(Wallet, 'create') + scope.createWallet (-> ) + expect(Wallet.create).toHaveBeenCalled() + ) + + it "should add uid to cookies", inject(($cookies) -> + spyOn($cookies, 'put') + scope.signup() + expect($cookies.put).toHaveBeenCalledWith('uid', "new_guid") + ) + + it "should add password to cookies in dev mode", inject(($cookies) -> + spyOn($cookies, 'put') + scope.savePassword = true + scope.fields.password = "testing" + + scope.signup() + expect($cookies.put).toHaveBeenCalledWith('password', "testing") + ) + + it "should not add password to cookies in production mode", inject(($cookies) -> + spyOn($cookies, 'put') + scope.savePassword = false + scope.fields.password = "testing" + + scope.signup() + expect($cookies.put).not.toHaveBeenCalledWith('password', "testing") + ) + + describe "language", -> it "should guess the correct language", -> - expect(scope.fields.language.code).toBe("en") + expect(scope.language_guess.code).toBe("en") - it "should switch interface language when new language is selected", inject(($translate) -> + it "should switch interface language to guessed language", inject(($translate, languages) -> spyOn($translate, "use") - expect(scope.fields.language.code).not.toBe(scope.languages[0].code) - scope.fields.language = scope.languages[0] + expect(scope.language_guess.code).not.toBe(languages[0].code) + scope.language_guess = languages[0] scope.$digest() - expect($translate.use).toHaveBeenCalledWith(scope.languages[0].code) + expect($translate.use).toHaveBeenCalledWith(languages[0].code) ) + describe "currency", -> it "should guess the correct currency", -> - expect(scope.fields.currency.code).toBe("USD") + expect(scope.currency_guess.code).toBe("USD") + + it "should switch to the guessed currency", inject((currency, Wallet) -> + spyOn(Wallet, "changeCurrency") + expect(scope.currency_guess.code).not.toBe(currency.currencies[1].code) + scope.currency_guess = currency.currencies[1] + scope.$digest() + expect(Wallet.changeCurrency).toHaveBeenCalledWith(currency.currencies[1]) + ) diff --git a/tests/mocks/my_wallet/my_wallet_mock.coffee b/tests/mocks/my_wallet/my_wallet_mock.coffee index bd5ac47747..280bb2bae2 100644 --- a/tests/mocks/my_wallet/my_wallet_mock.coffee +++ b/tests/mocks/my_wallet/my_wallet_mock.coffee @@ -1,4 +1,4 @@ angular .module('walletApp.core') - .factory 'MyWallet', ($window, $timeout, $log, $cookieStore, MyWalletStore) -> + .factory 'MyWallet', ($window, $timeout, $log, MyWalletStore) -> return {} diff --git a/tests/mocks/my_wallet/my_wallet_token_endpoints.coffee b/tests/mocks/my_wallet/my_wallet_token_endpoints.coffee new file mode 100644 index 0000000000..04c2070a7b --- /dev/null +++ b/tests/mocks/my_wallet/my_wallet_token_endpoints.coffee @@ -0,0 +1,2 @@ +angular.module('walletApp.core').factory 'MyWalletTokenEndpoints', () -> + this diff --git a/tests/services/adverts_service_spec.coffee b/tests/services/adverts_service_spec.coffee index 70e2f6d820..a9c5e001f8 100644 --- a/tests/services/adverts_service_spec.coffee +++ b/tests/services/adverts_service_spec.coffee @@ -3,44 +3,45 @@ describe "AdvertsServices", () -> rootScope = undefined sampleAd = '{"partners":{"home_buttons":[{"image":"data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAANCAYAAACgu+4kAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN\/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz\/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH\/w\/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA\/g88wAAKCRFRHgg\/P9eM4Ors7ONo62Dl8t6r8G\/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt\/qIl7gRoXgugdfeLZrIPQLUAoOnaV\/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl\/AV\/1s+X48\/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H\/LcL\/\/wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93\/+8\/\/UegJQCAZkmScQAAXkQkLlTKsz\/HCAAARKCBKrBBG\/TBGCzABhzBBdzBC\/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD\/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q\/pH5Z\/YkGWcNMw09DpFGgsV\/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY\/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4\/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L\/1U\/W36p\/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N\/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26\/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE\/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV\/MN8C3yLfLT8Nvnl+F30N\/I\/9k\/3r\/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt\/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi\/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a\/zYnKOZarnivN7cyzytuQN5zvn\/\/tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO\/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3\/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA\/0HIw6217nU1R3SPVRSj9Yr60cOxx++\/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3\/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX\/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8\/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb\/1tWeOT3dvfN6b\/fF9\/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR\/cGhYPP\/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF\/6i\/suuFxYvfvjV69fO0ZjRoZfyl5O\/bXyl\/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o\/2j5sfVT0Kf7kxmTk\/8EA5jz\/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5\/wAAgOkAAHUwAADqYAAAOpgAABdvkl\/FRgAAAPdJREFUeNqc0j8rBlAcxfE7KDGiDBZKksFMSSwmi4VFTMoqUUpiUCzegWzKn5JMDBZ5ARJFUmxSBjF\/LOeppycSp+7wPefeM\/zur6DUnDr0YR5b2MA0Or+5W2qNXhz5Xq\/YRMNPBRN487su0F5bMORvOkdjpaAJt\/6uhUrBpP\/pCS0FB\/6v0YKbwA76cRZ+zmzmqx4sYxiP4aWCh8BIBjobPg434CNec7zt8FrBXWAs4Vz4JNyC93gd8XbCKwX3gfGEi+HTcCs+43XF2w2vFrwEphKuVC1MQVvVDHriHYbXS3b9Et0JB3GFmXA99rBfWZ58\/TUGvgYAVx\/rST0ljjAAAAAASUVORK5CYII=","country_code":"EU","link":"https:\/\/blockchain.info\/r?url=https%3A%2F%2Fwww.kraken.com%2F&aid=3587","button_class":"btn-primary","id":3587,"type":4,"title":"Kraken Deposit","status":0}]}}' $httpBackend = undefined - + beforeEach angular.mock.module("walletApp") - + beforeEach -> angular.mock.inject ($injector, _$rootScope_) -> - + $httpBackend = $injector.get("$httpBackend") - + Adverts = $injector.get("Adverts") - rootScope = _$rootScope_ - + rootScope = _$rootScope_ + return - return - - describe "fetch()", -> + return + + describe "fetch()", -> beforeEach -> + rootScope.rootURL = "https://blockchain.info/" $httpBackend.expectGET("https://blockchain.info/adverts_feed?wallet_version=3").respond sampleAd - + Adverts.fetch() - + $httpBackend.flush() - + it "should download the most recent adverts feed", -> expect(Adverts.ads.length).toBe 2 - + it "should get the ad title", -> expect(Adverts.ads[0].title).toBe "Kraken Deposit" - + it "should get the ad URL", -> expect(Adverts.ads[0].link).toContain "http" - + it "should get the image blob", -> expect(Adverts.ads[0].image).toContain "data:image" - + it "should get a class to add to the button", -> expect(Adverts.ads[0].button_class).toBeDefined() - + describe "fetchOnce()", -> it "should call fetch() only once", -> spyOn(Adverts, "fetch") @@ -49,6 +50,6 @@ describe "AdvertsServices", () -> Adverts.fetchOnce() expect(Adverts.fetch.calls.count()).toBe(1) - afterEach -> + afterEach -> $httpBackend.verifyNoOutstandingExpectation() $httpBackend.verifyNoOutstandingRequest()