├── .babelrc
├── .env.example
├── .gitignore
├── license
├── package-lock.json
├── package.json
├── readme.md
├── scatter.zip
├── src
├── background.js
├── components
│ ├── ButtonComponent.vue
│ ├── InputComponent.vue
│ ├── KeyValue.vue
│ ├── NavActionsComponent.vue
│ ├── NavbarComponent.vue
│ ├── SearchComponent.vue
│ ├── SelectComponent.vue
│ └── alerts
│ │ └── Alert.vue
├── content.js
├── copied
│ ├── assets
│ │ └── fonts
│ │ │ └── font-awesome
│ │ │ ├── HELP-US-OUT.txt
│ │ │ ├── css
│ │ │ ├── font-awesome.css
│ │ │ └── font-awesome.min.css
│ │ │ ├── fonts
│ │ │ ├── FontAwesome.otf
│ │ │ ├── fontawesome-webfont.eot
│ │ │ ├── fontawesome-webfont.svg
│ │ │ ├── fontawesome-webfont.ttf
│ │ │ ├── fontawesome-webfont.woff
│ │ │ └── fontawesome-webfont.woff2
│ │ │ ├── less
│ │ │ ├── animated.less
│ │ │ ├── bordered-pulled.less
│ │ │ ├── core.less
│ │ │ ├── fixed-width.less
│ │ │ ├── font-awesome.less
│ │ │ ├── icons.less
│ │ │ ├── larger.less
│ │ │ ├── list.less
│ │ │ ├── mixins.less
│ │ │ ├── path.less
│ │ │ ├── rotated-flipped.less
│ │ │ ├── screen-reader.less
│ │ │ ├── stacked.less
│ │ │ └── variables.less
│ │ │ └── scss
│ │ │ ├── _animated.scss
│ │ │ ├── _bordered-pulled.scss
│ │ │ ├── _core.scss
│ │ │ ├── _fixed-width.scss
│ │ │ ├── _icons.scss
│ │ │ ├── _larger.scss
│ │ │ ├── _list.scss
│ │ │ ├── _mixins.scss
│ │ │ ├── _path.scss
│ │ │ ├── _rotated-flipped.scss
│ │ │ ├── _screen-reader.scss
│ │ │ ├── _stacked.scss
│ │ │ ├── _variables.scss
│ │ │ └── font-awesome.scss
│ ├── icon.png
│ ├── index.html
│ ├── manifest.json
│ └── prompt.html
├── data
│ └── Countries.js
├── inject.js
├── localization
│ ├── keys.js
│ ├── languages
│ │ ├── chinese.js
│ │ ├── english.js
│ │ ├── french.js
│ │ ├── german.js
│ │ ├── hebrew.js
│ │ ├── korean.js
│ │ ├── portuguese.js
│ │ ├── slovene.js
│ │ └── spanish.js
│ ├── locales.js
│ └── readme.md
├── messages
│ ├── InternalMessage.js
│ ├── InternalMessageTypes.js
│ ├── NetworkMessage.js
│ ├── NetworkMessageTypes.js
│ └── PairingTags.js
├── migrations
│ ├── migrator.js
│ └── versions
│ │ ├── 4.0.1.js
│ │ ├── 5.0.2.js
│ │ ├── 5.0.4.js
│ │ ├── 6.0.0.js
│ │ ├── 6.0.4.js
│ │ └── version.js
├── models
│ ├── Account.js
│ ├── Blockchains.js
│ ├── Identity.js
│ ├── KeyPair.js
│ ├── Keychain.js
│ ├── Meta.js
│ ├── Network.js
│ ├── Permission.js
│ ├── Scatter.js
│ ├── Settings.js
│ ├── alerts
│ │ ├── AlertMsg.js
│ │ └── AlertTypes.js
│ ├── errors
│ │ ├── Error.js
│ │ └── ErrorTypes.js
│ ├── histories
│ │ ├── HistoricEvent.js
│ │ └── HistoricEventTypes.js
│ └── prompts
│ │ ├── Prompt.js
│ │ └── PromptTypes.js
├── plugins
│ ├── Plugin.js
│ ├── PluginRepository.js
│ ├── PluginTypes.js
│ └── defaults
│ │ ├── eos.js
│ │ └── eth.js
├── popup.js
├── prompt.js
├── prompts
│ ├── PromptBase.vue
│ ├── RequestAddNetwork.vue
│ ├── RequestArbitrarySignaturePrompt.vue
│ ├── RequestIdentityPrompt.vue
│ ├── RequestSignaturePrompt.vue
│ ├── RequestUnlock.vue
│ └── UpdateVersion.vue
├── scatterdapp.js
├── services
│ ├── AccountService.js
│ ├── AuthenticationService.js
│ ├── IdentityService.js
│ ├── KeyPairService.js
│ ├── NotificationService.js
│ ├── RIDLService.js
│ ├── SignatureService.js
│ └── StorageService.js
├── store
│ ├── actions.js
│ ├── constants.js
│ ├── mutations.js
│ └── store.js
├── styles.scss
├── util
│ ├── BrowserApis.js
│ ├── ContractHelpers.js
│ ├── EOSKeygen.js
│ ├── GenericTools.js
│ ├── Hasher.js
│ ├── IdGenerator.js
│ ├── Mnemonic.js
│ ├── ObjectHelpers.js
│ ├── StringHelpers.js
│ └── TimingHelpers.js
├── views
│ ├── AutoLockView.vue
│ ├── BackupView.vue
│ ├── ChangePasswordView.vue
│ ├── DestroyView.vue
│ ├── DomainPermissionView.vue
│ ├── EntryView.vue
│ ├── HistoryView.vue
│ ├── IdentitiesView.vue
│ ├── IdentityView.vue
│ ├── KeyPairView.vue
│ ├── KeyPairsView.vue
│ ├── LanguageView.vue
│ ├── LoadFromBackup.vue
│ ├── MainMenuView.vue
│ ├── NetworkView.vue
│ ├── NetworksView.vue
│ ├── Onboarding.vue
│ ├── PermissionsView.vue
│ ├── SettingsView.vue
│ ├── ShowMnemonicView.vue
│ └── ViewBase.vue
└── vue
│ ├── Routing.js
│ └── VueInitializer.js
├── tests
├── helpers
│ └── chrome.js
├── localization
│ └── locales.spec.js
├── migrations
│ ├── 5.0.4.spec.js
│ └── 6.0.4.spec.js
├── models
│ └── Identity.spec.js
├── pk2pk.spec.js
├── services
│ └── StorageService.spec.js
├── setup.js
├── signers
│ └── eos.spec.js
└── util
│ ├── ContractHelpers.spec.js
│ ├── IdGenerator.spec.js
│ └── ObjectHelpers.spec.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-3"],
3 | "plugins": [
4 | ["transform-runtime", {
5 | "regenerator": true
6 | }
7 | ]
8 | ]
9 | }
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | SCATTER_ENV=production
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | node_modules/
3 | build/
4 | .env
5 | .DS_Store
6 | *.iml
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | Copyright 2018 EOS Essentials
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scatter",
3 | "version": "6.3.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "set SCATTER_ENV=testing&& mocha-webpack --timeout 1000000 --webpack-config webpack.config.js --require tests/setup.js \"tests/**/*.spec.js\"",
8 | "testfile": "set SCATTER_ENV=testing&& mocha-webpack --timeout 1000000 --webpack-config webpack.config.js --require tests/setup.js ",
9 | "start": "set SCATTER_ENV=development&& webpack --watch",
10 | "build": "set SCATTER_ENV=production&& webpack"
11 | },
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "aes-oop": "^1.0.4",
16 | "bip39": "^2.5.0",
17 | "dotenv-webpack": "^1.5.7",
18 | "eos-rc-parser": "^1.0.4",
19 | "eosjs": "^16.0.5",
20 | "eosjs-api": "^6.3.2",
21 | "eosjs-ecc": "^4.0.3",
22 | "ethereumjs-abi": "^0.6.5",
23 | "ethereumjs-tx": "^1.3.7",
24 | "ethereumjs-util": "^5.2.0",
25 | "extension-streams": "^1.0.7",
26 | "fcbuffer": "^2.2.1",
27 | "ridl": "^0.1.0",
28 | "scrypt-async": "^2.0.0",
29 | "secp256k1": "^3.5.0",
30 | "vue": "^2.5.17",
31 | "vue-moment": "^3.2.0",
32 | "vue-router": "^3.0.1",
33 | "vuex": "^3.0.1",
34 | "web3": "^1.0.0-beta2",
35 | "web3-provider-engine": "^14.1.0"
36 | },
37 | "devDependencies": {
38 | "babel-core": "^6.26.3",
39 | "babel-loader": "^7.1.5",
40 | "babel-plugin-transform-runtime": "^6.23.0",
41 | "babel-preset-es2015": "^6.24.1",
42 | "babel-preset-stage-3": "^6.24.1",
43 | "chai": "^4.1.2",
44 | "copy-webpack-plugin": "^4.5.2",
45 | "css-loader": "^0.28.11",
46 | "extract-text-webpack-plugin": "^3.0.2",
47 | "file-loader": "^1.1.11",
48 | "ignore-emit-webpack-plugin": "^0.2.1",
49 | "jsdom": "^11.12.0",
50 | "jsdom-global": "^3.0.2",
51 | "mocha": "^5.2.0",
52 | "mocha-webpack": "^1.1.0",
53 | "node-sass": "^4.9.3",
54 | "sass-loader": "^6.0.7",
55 | "uglifyjs-webpack-plugin": "^1.3.0",
56 | "vue-loader": "^13.7.2",
57 | "vue-template-compiler": "^2.5.17",
58 | "webpack": "^3.12.0",
59 | "webpack-node-externals": "^1.7.2",
60 | "zip-webpack-plugin": "^2.1.0"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Scatter Classic
2 |
3 | Scatter is a browser extension that allows you to sign transactions for multiple blockchains and provide personal information to web applications without ever exposing your keys or filling out forms.
4 |
5 | # NOTICE: Scatter Classic is DEPRECATED!
6 | ## Do not build apps only for Scatter Classic ( Extension ).
7 |
8 | ### Visit https://get-scatter.com/docs/dev/setting-up-for-web-apps to find out how to make dapps that support Classic, Desktop and Mobile all at once.
9 |
10 | If you want more information about this decision please read this article: https://medium.com/@nsjames/the-blockchain-isnt-just-for-web-applications-silly-rabbit-926a4ea5ccd1
11 |
12 |
13 |
--------------------------------------------------------------------------------
/scatter.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetScatter/ScatterWebExtension/0b9b3818a7ca1a0fa6982f8d7f703f05e9f60d69/scatter.zip
--------------------------------------------------------------------------------
/src/components/ButtonComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/InputComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
30 |
31 |
--------------------------------------------------------------------------------
/src/components/KeyValue.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
31 |
--------------------------------------------------------------------------------
/src/components/NavActionsComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{action.text}}
6 |
7 |
8 |
9 |
10 |
21 |
22 |
78 |
--------------------------------------------------------------------------------
/src/components/NavbarComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
77 |
78 |
--------------------------------------------------------------------------------
/src/components/SearchComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
18 |
19 |
--------------------------------------------------------------------------------
/src/components/SelectComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{parse(selectedOption)}}
9 |
10 |
11 |
12 |
13 | {{parse(item)}}
14 |
15 |
16 |
17 |
18 |
19 |
53 |
54 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/HELP-US-OUT.txt:
--------------------------------------------------------------------------------
1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project,
2 | Fort Awesome (https://fortawesome.com). It makes it easy to put the perfect icons on your website. Choose from our awesome,
3 | comprehensive icon sets or copy and paste your own.
4 |
5 | Please. Check it out.
6 |
7 | -Dave Gandy
8 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetScatter/ScatterWebExtension/0b9b3818a7ca1a0fa6982f8d7f703f05e9f60d69/src/copied/assets/fonts/font-awesome/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetScatter/ScatterWebExtension/0b9b3818a7ca1a0fa6982f8d7f703f05e9f60d69/src/copied/assets/fonts/font-awesome/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetScatter/ScatterWebExtension/0b9b3818a7ca1a0fa6982f8d7f703f05e9f60d69/src/copied/assets/fonts/font-awesome/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetScatter/ScatterWebExtension/0b9b3818a7ca1a0fa6982f8d7f703f05e9f60d69/src/copied/assets/fonts/font-awesome/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetScatter/ScatterWebExtension/0b9b3818a7ca1a0fa6982f8d7f703f05e9f60d69/src/copied/assets/fonts/font-awesome/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/less/animated.less:
--------------------------------------------------------------------------------
1 | // Animated Icons
2 | // --------------------------
3 |
4 | .@{fa-css-prefix}-spin {
5 | -webkit-animation: fa-spin 2s infinite linear;
6 | animation: fa-spin 2s infinite linear;
7 | }
8 |
9 | .@{fa-css-prefix}-pulse {
10 | -webkit-animation: fa-spin 1s infinite steps(8);
11 | animation: fa-spin 1s infinite steps(8);
12 | }
13 |
14 | @-webkit-keyframes fa-spin {
15 | 0% {
16 | -webkit-transform: rotate(0deg);
17 | transform: rotate(0deg);
18 | }
19 | 100% {
20 | -webkit-transform: rotate(359deg);
21 | transform: rotate(359deg);
22 | }
23 | }
24 |
25 | @keyframes fa-spin {
26 | 0% {
27 | -webkit-transform: rotate(0deg);
28 | transform: rotate(0deg);
29 | }
30 | 100% {
31 | -webkit-transform: rotate(359deg);
32 | transform: rotate(359deg);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/less/bordered-pulled.less:
--------------------------------------------------------------------------------
1 | // Bordered & Pulled
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-border {
5 | padding: .2em .25em .15em;
6 | border: solid .08em @fa-border-color;
7 | border-radius: .1em;
8 | }
9 |
10 | .@{fa-css-prefix}-pull-left { float: left; }
11 | .@{fa-css-prefix}-pull-right { float: right; }
12 |
13 | .@{fa-css-prefix} {
14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; }
15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; }
16 | }
17 |
18 | /* Deprecated as of 4.4.0 */
19 | .pull-right { float: right; }
20 | .pull-left { float: left; }
21 |
22 | .@{fa-css-prefix} {
23 | &.pull-left { margin-right: .3em; }
24 | &.pull-right { margin-left: .3em; }
25 | }
26 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/less/core.less:
--------------------------------------------------------------------------------
1 | // Base Class Definition
2 | // -------------------------
3 |
4 | .@{fa-css-prefix} {
5 | display: inline-block;
6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/less/fixed-width.less:
--------------------------------------------------------------------------------
1 | // Fixed Width Icons
2 | // -------------------------
3 | .@{fa-css-prefix}-fw {
4 | width: (18em / 14);
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/less/font-awesome.less:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */
5 |
6 | @import "variables.less";
7 | @import "mixins.less";
8 | @import "path.less";
9 | @import "core.less";
10 | @import "larger.less";
11 | @import "fixed-width.less";
12 | @import "list.less";
13 | @import "bordered-pulled.less";
14 | @import "animated.less";
15 | @import "rotated-flipped.less";
16 | @import "stacked.less";
17 | @import "icons.less";
18 | @import "screen-reader.less";
19 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/less/larger.less:
--------------------------------------------------------------------------------
1 | // Icon Sizes
2 | // -------------------------
3 |
4 | /* makes the font 33% larger relative to the icon container */
5 | .@{fa-css-prefix}-lg {
6 | font-size: (4em / 3);
7 | line-height: (3em / 4);
8 | vertical-align: -15%;
9 | }
10 | .@{fa-css-prefix}-2x { font-size: 2em; }
11 | .@{fa-css-prefix}-3x { font-size: 3em; }
12 | .@{fa-css-prefix}-4x { font-size: 4em; }
13 | .@{fa-css-prefix}-5x { font-size: 5em; }
14 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/less/list.less:
--------------------------------------------------------------------------------
1 | // List Icons
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-ul {
5 | padding-left: 0;
6 | margin-left: @fa-li-width;
7 | list-style-type: none;
8 | > li { position: relative; }
9 | }
10 | .@{fa-css-prefix}-li {
11 | position: absolute;
12 | left: -@fa-li-width;
13 | width: @fa-li-width;
14 | top: (2em / 14);
15 | text-align: center;
16 | &.@{fa-css-prefix}-lg {
17 | left: (-@fa-li-width + (4em / 14));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/less/mixins.less:
--------------------------------------------------------------------------------
1 | // Mixins
2 | // --------------------------
3 |
4 | .fa-icon() {
5 | display: inline-block;
6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
14 | .fa-icon-rotate(@degrees, @rotation) {
15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})";
16 | -webkit-transform: rotate(@degrees);
17 | -ms-transform: rotate(@degrees);
18 | transform: rotate(@degrees);
19 | }
20 |
21 | .fa-icon-flip(@horiz, @vert, @rotation) {
22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)";
23 | -webkit-transform: scale(@horiz, @vert);
24 | -ms-transform: scale(@horiz, @vert);
25 | transform: scale(@horiz, @vert);
26 | }
27 |
28 |
29 | // Only display content to screen readers. A la Bootstrap 4.
30 | //
31 | // See: http://a11yproject.com/posts/how-to-hide-content/
32 |
33 | .sr-only() {
34 | position: absolute;
35 | width: 1px;
36 | height: 1px;
37 | padding: 0;
38 | margin: -1px;
39 | overflow: hidden;
40 | clip: rect(0,0,0,0);
41 | border: 0;
42 | }
43 |
44 | // Use in conjunction with .sr-only to only display content when it's focused.
45 | //
46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
47 | //
48 | // Credit: HTML5 Boilerplate
49 |
50 | .sr-only-focusable() {
51 | &:active,
52 | &:focus {
53 | position: static;
54 | width: auto;
55 | height: auto;
56 | margin: 0;
57 | overflow: visible;
58 | clip: auto;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/less/path.less:
--------------------------------------------------------------------------------
1 | /* FONT PATH
2 | * -------------------------- */
3 |
4 | @font-face {
5 | font-family: 'FontAwesome';
6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}');
7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'),
8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'),
9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'),
10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'),
11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg');
12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/less/rotated-flipped.less:
--------------------------------------------------------------------------------
1 | // Rotated & Flipped Icons
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); }
5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); }
6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); }
7 |
8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); }
9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); }
10 |
11 | // Hook for IE8-9
12 | // -------------------------
13 |
14 | :root .@{fa-css-prefix}-rotate-90,
15 | :root .@{fa-css-prefix}-rotate-180,
16 | :root .@{fa-css-prefix}-rotate-270,
17 | :root .@{fa-css-prefix}-flip-horizontal,
18 | :root .@{fa-css-prefix}-flip-vertical {
19 | filter: none;
20 | }
21 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/less/screen-reader.less:
--------------------------------------------------------------------------------
1 | // Screen Readers
2 | // -------------------------
3 |
4 | .sr-only { .sr-only(); }
5 | .sr-only-focusable { .sr-only-focusable(); }
6 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/less/stacked.less:
--------------------------------------------------------------------------------
1 | // Stacked Icons
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-stack {
5 | position: relative;
6 | display: inline-block;
7 | width: 2em;
8 | height: 2em;
9 | line-height: 2em;
10 | vertical-align: middle;
11 | }
12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x {
13 | position: absolute;
14 | left: 0;
15 | width: 100%;
16 | text-align: center;
17 | }
18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; }
19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; }
20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; }
21 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/scss/_animated.scss:
--------------------------------------------------------------------------------
1 | // Spinning Icons
2 | // --------------------------
3 |
4 | .#{$fa-css-prefix}-spin {
5 | -webkit-animation: fa-spin 2s infinite linear;
6 | animation: fa-spin 2s infinite linear;
7 | }
8 |
9 | .#{$fa-css-prefix}-pulse {
10 | -webkit-animation: fa-spin 1s infinite steps(8);
11 | animation: fa-spin 1s infinite steps(8);
12 | }
13 |
14 | @-webkit-keyframes fa-spin {
15 | 0% {
16 | -webkit-transform: rotate(0deg);
17 | transform: rotate(0deg);
18 | }
19 | 100% {
20 | -webkit-transform: rotate(359deg);
21 | transform: rotate(359deg);
22 | }
23 | }
24 |
25 | @keyframes fa-spin {
26 | 0% {
27 | -webkit-transform: rotate(0deg);
28 | transform: rotate(0deg);
29 | }
30 | 100% {
31 | -webkit-transform: rotate(359deg);
32 | transform: rotate(359deg);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/scss/_bordered-pulled.scss:
--------------------------------------------------------------------------------
1 | // Bordered & Pulled
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-border {
5 | padding: .2em .25em .15em;
6 | border: solid .08em $fa-border-color;
7 | border-radius: .1em;
8 | }
9 |
10 | .#{$fa-css-prefix}-pull-left { float: left; }
11 | .#{$fa-css-prefix}-pull-right { float: right; }
12 |
13 | .#{$fa-css-prefix} {
14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; }
15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; }
16 | }
17 |
18 | /* Deprecated as of 4.4.0 */
19 | .pull-right { float: right; }
20 | .pull-left { float: left; }
21 |
22 | .#{$fa-css-prefix} {
23 | &.pull-left { margin-right: .3em; }
24 | &.pull-right { margin-left: .3em; }
25 | }
26 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/scss/_core.scss:
--------------------------------------------------------------------------------
1 | // Base Class Definition
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix} {
5 | display: inline-block;
6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/scss/_fixed-width.scss:
--------------------------------------------------------------------------------
1 | // Fixed Width Icons
2 | // -------------------------
3 | .#{$fa-css-prefix}-fw {
4 | width: (18em / 14);
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/scss/_larger.scss:
--------------------------------------------------------------------------------
1 | // Icon Sizes
2 | // -------------------------
3 |
4 | /* makes the font 33% larger relative to the icon container */
5 | .#{$fa-css-prefix}-lg {
6 | font-size: (4em / 3);
7 | line-height: (3em / 4);
8 | vertical-align: -15%;
9 | }
10 | .#{$fa-css-prefix}-2x { font-size: 2em; }
11 | .#{$fa-css-prefix}-3x { font-size: 3em; }
12 | .#{$fa-css-prefix}-4x { font-size: 4em; }
13 | .#{$fa-css-prefix}-5x { font-size: 5em; }
14 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/scss/_list.scss:
--------------------------------------------------------------------------------
1 | // List Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-ul {
5 | padding-left: 0;
6 | margin-left: $fa-li-width;
7 | list-style-type: none;
8 | > li { position: relative; }
9 | }
10 | .#{$fa-css-prefix}-li {
11 | position: absolute;
12 | left: -$fa-li-width;
13 | width: $fa-li-width;
14 | top: (2em / 14);
15 | text-align: center;
16 | &.#{$fa-css-prefix}-lg {
17 | left: -$fa-li-width + (4em / 14);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/scss/_mixins.scss:
--------------------------------------------------------------------------------
1 | // Mixins
2 | // --------------------------
3 |
4 | @mixin fa-icon() {
5 | display: inline-block;
6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
14 | @mixin fa-icon-rotate($degrees, $rotation) {
15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})";
16 | -webkit-transform: rotate($degrees);
17 | -ms-transform: rotate($degrees);
18 | transform: rotate($degrees);
19 | }
20 |
21 | @mixin fa-icon-flip($horiz, $vert, $rotation) {
22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)";
23 | -webkit-transform: scale($horiz, $vert);
24 | -ms-transform: scale($horiz, $vert);
25 | transform: scale($horiz, $vert);
26 | }
27 |
28 |
29 | // Only display content to screen readers. A la Bootstrap 4.
30 | //
31 | // See: http://a11yproject.com/posts/how-to-hide-content/
32 |
33 | @mixin sr-only {
34 | position: absolute;
35 | width: 1px;
36 | height: 1px;
37 | padding: 0;
38 | margin: -1px;
39 | overflow: hidden;
40 | clip: rect(0,0,0,0);
41 | border: 0;
42 | }
43 |
44 | // Use in conjunction with .sr-only to only display content when it's focused.
45 | //
46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
47 | //
48 | // Credit: HTML5 Boilerplate
49 |
50 | @mixin sr-only-focusable {
51 | &:active,
52 | &:focus {
53 | position: static;
54 | width: auto;
55 | height: auto;
56 | margin: 0;
57 | overflow: visible;
58 | clip: auto;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/scss/_path.scss:
--------------------------------------------------------------------------------
1 | /* FONT PATH
2 | * -------------------------- */
3 |
4 | @font-face {
5 | font-family: 'FontAwesome';
6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}');
7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'),
8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'),
9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'),
10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'),
11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg');
12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/scss/_rotated-flipped.scss:
--------------------------------------------------------------------------------
1 | // Rotated & Flipped Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); }
5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); }
6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); }
7 |
8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); }
9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); }
10 |
11 | // Hook for IE8-9
12 | // -------------------------
13 |
14 | :root .#{$fa-css-prefix}-rotate-90,
15 | :root .#{$fa-css-prefix}-rotate-180,
16 | :root .#{$fa-css-prefix}-rotate-270,
17 | :root .#{$fa-css-prefix}-flip-horizontal,
18 | :root .#{$fa-css-prefix}-flip-vertical {
19 | filter: none;
20 | }
21 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/scss/_screen-reader.scss:
--------------------------------------------------------------------------------
1 | // Screen Readers
2 | // -------------------------
3 |
4 | .sr-only { @include sr-only(); }
5 | .sr-only-focusable { @include sr-only-focusable(); }
6 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/scss/_stacked.scss:
--------------------------------------------------------------------------------
1 | // Stacked Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-stack {
5 | position: relative;
6 | display: inline-block;
7 | width: 2em;
8 | height: 2em;
9 | line-height: 2em;
10 | vertical-align: middle;
11 | }
12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x {
13 | position: absolute;
14 | left: 0;
15 | width: 100%;
16 | text-align: center;
17 | }
18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; }
19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; }
20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; }
21 |
--------------------------------------------------------------------------------
/src/copied/assets/fonts/font-awesome/scss/font-awesome.scss:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */
5 |
6 | @import "variables";
7 | @import "mixins";
8 | @import "path";
9 | @import "core";
10 | @import "larger";
11 | @import "fixed-width";
12 | @import "list";
13 | @import "bordered-pulled";
14 | @import "animated";
15 | @import "rotated-flipped";
16 | @import "stacked";
17 | @import "icons";
18 | @import "screen-reader";
19 |
--------------------------------------------------------------------------------
/src/copied/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GetScatter/ScatterWebExtension/0b9b3818a7ca1a0fa6982f8d7f703f05e9f60d69/src/copied/icon.png
--------------------------------------------------------------------------------
/src/copied/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Scatter
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/copied/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Scatter",
4 | "description": "Decentralized signature, identity and authentication system.",
5 | "version": "6.3.0",
6 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
7 | "browser_action": {
8 | "default_icon": "icon.png",
9 | "default_popup": "index.html"
10 | },
11 | "permissions": [
12 | "storage",
13 | "unlimitedStorage",
14 | "http://*/*",
15 | "https://*/*"
16 | ],
17 | "content_scripts": [
18 | {
19 | "matches": [
20 | "http://*/*",
21 | "https://*/*"
22 | ],
23 | "js": ["content.js"],
24 | "run_at": "document_end"
25 | }
26 | ],
27 | "web_accessible_resources": [
28 | "inject.js"
29 | ],
30 | "background":{
31 | "scripts":[
32 | "background.js"
33 | ]
34 | }
35 | }
--------------------------------------------------------------------------------
/src/copied/prompt.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Scatter
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/inject.js:
--------------------------------------------------------------------------------
1 | import IdGenerator from './util/IdGenerator';
2 | import {EncryptedStream} from 'extension-streams';
3 | import * as PairingTags from './messages/PairingTags'
4 | import * as NetworkMessageTypes from './messages/NetworkMessageTypes'
5 | import Scatterdapp from './scatterdapp'
6 |
7 | /***
8 | * This is the javascript which gets injected into
9 | * the application and facilitates communication between
10 | * Scatter and the web application.
11 | */
12 | class Inject {
13 |
14 | constructor(){
15 | // Injecting an encrypted stream into the
16 | // web application.
17 | // const stream = new EncryptedStream(PairingTags.INJECTED, IdGenerator.text(64));
18 | //
19 | // // Waiting for scatter to push itself onto the application
20 | // stream.listenWith(msg => {
21 | // if(msg && msg.hasOwnProperty('type') && msg.type === NetworkMessageTypes.PUSH_SCATTER)
22 | // window.scatter = new Scatterdapp(stream, msg.payload);
23 | // });
24 | //
25 | // // Syncing the streams between the
26 | // // extension and the web application
27 | // stream.sync(PairingTags.SCATTER, stream.key);
28 | }
29 |
30 | }
31 |
32 | new Inject();
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/localization/locales.js:
--------------------------------------------------------------------------------
1 | import * as KEYS from './keys';
2 |
3 | import english from './languages/english';
4 | import french from './languages/french';
5 | import portuguese from './languages/portuguese';
6 | import spanish from './languages/spanish';
7 | import hebrew from './languages/hebrew';
8 | import slovene from './languages/slovene';
9 | import german from './languages/german';
10 | import korean from './languages/korean';
11 | import chinese from './languages/chinese';
12 |
13 | export const LANG = {
14 | ENGLISH: 'English',
15 | FRENCH: 'Français ( French )',
16 | PORTUGUESE: 'Português ( Portuguese )',
17 | SPANISH: 'Español ( Spanish )',
18 | SLOVENE: 'Slovensko ( Slovene )',
19 | HEBREW: 'עברית ( Hebrew )',
20 | GERMAN: 'Deutsch ( German )',
21 | KOREAN: '한국어 ( Korean )',
22 | CHINESE: '中文 ( Chinese )',
23 | };
24 |
25 | const languages = {
26 | [LANG.ENGLISH]:english,
27 | [LANG.FRENCH]:french,
28 | [LANG.PORTUGUESE]:portuguese,
29 | [LANG.SPANISH]:spanish,
30 | [LANG.SLOVENE]:slovene,
31 | [LANG.HEBREW]:hebrew,
32 | [LANG.GERMAN]:german,
33 | [LANG.KOREAN]:korean,
34 | [LANG.CHINESE]:chinese,
35 | };
36 |
37 | export const locales = () => {
38 | return Object.keys(LANG).reduce((langobj, lang) => {
39 | langobj[lang] = Object.keys(KEYS).reduce((keyobj, key) => {
40 | keyobj[KEYS[key]] = languages[LANG[lang]][KEYS[key]];
41 | return keyobj;
42 | }, {});
43 | return langobj;
44 | }, {});
45 | };
46 |
47 | export const getLangKey = lang => Object.keys(LANG).find(key => LANG[key] === lang);
48 | export const localized = (key, language) => locales()[language][key] || locales()['ENGLISH'][key]
49 |
--------------------------------------------------------------------------------
/src/localization/readme.md:
--------------------------------------------------------------------------------
1 | # Help add languages.
2 |
3 | The first contributor of every new language gets their name in the main `readme.md` file.
4 |
5 |
6 | ### Adding a new language
7 |
8 | -----------
9 |
10 | #### Before you do anything,
11 | Open a ticket in the `Issues` panel stating that you are adding a language so that multiple people don't work on the same thing.
12 | **Use the following format:**
13 |
14 | **Adding Language: {LANGUAGE NAME HERE}**
15 |
16 | ---------
17 |
18 |
19 | ##### First copy the `src/localization/languages/english.js` file into a new `.js` file in the same directory.
20 | Once you have copied it go to the `src/localization/locales.js` file and add it to the arrays/objects.
21 |
22 | For example, if we were adding chinese.
23 | ```js
24 | import english from './languages/english'
25 | import chinese from './languages/chinese'
26 |
27 |
28 | export const LANG = {
29 | ENGLISH:'English',
30 | CHINESE:'中文 ( Chinese )'
31 | };
32 |
33 | const languages = {
34 | [LANG.ENGLISH]:english,
35 | [LANG.CHINESE]:chinese
36 | };
37 | ```
38 |
39 | **Always add an english version in parenthesis.**
40 |
41 | While you're translating the new language file try to keep string lengths the same or less. In some places the lengths of the strings are pushing the bounds of their containers and if you translate into longer strings there's a chance they will get cut off by a popup's bounding box.
42 |
43 |
--------------------------------------------------------------------------------
/src/messages/InternalMessage.js:
--------------------------------------------------------------------------------
1 | import {LocalStream} from 'extension-streams';
2 |
3 | export default class InternalMessage {
4 |
5 | constructor(){
6 | this.type = '';
7 | this.payload = '';
8 | }
9 |
10 | static placeholder(){ return new InternalMessage(); }
11 | static fromJson(json){ return Object.assign(this.placeholder(), json); }
12 |
13 | static payload(type, payload){
14 | let p = this.placeholder();
15 | p.type = type;
16 | p.payload = payload;
17 | return p;
18 | }
19 |
20 | static signal(type){
21 | let p = this.placeholder();
22 | p.type = type;
23 | return p;
24 | }
25 |
26 | send(){
27 | return LocalStream.send(this);
28 | }
29 | }
--------------------------------------------------------------------------------
/src/messages/InternalMessageTypes.js:
--------------------------------------------------------------------------------
1 | export const SET_SEED = 'setSeed';
2 | export const GET_SEED = 'getSeed';
3 | export const SET_TIMEOUT = 'setTimeout';
4 | export const IS_UNLOCKED = 'isUnlocked';
5 | export const LOAD = 'load';
6 | export const UPDATE = 'update';
7 | export const PUB_TO_PRIV = 'publicToPrivateKey';
8 | export const DESTROY = 'destroy';
9 |
10 | export const IDENTITY_FROM_PERMISSIONS = 'identityFromPermissions';
11 | export const ABI_CACHE = 'abiCache';
12 | export const GET_OR_REQUEST_IDENTITY = 'getOrRequestIdentity';
13 | export const FORGET_IDENTITY = 'forgetIdentity';
14 | export const REQUEST_SIGNATURE = 'requestSignature';
15 | export const REQUEST_ARBITRARY_SIGNATURE = 'requestArbitrarySignature';
16 | export const REQUEST_ADD_NETWORK = 'requestAddNetwork';
17 | export const REQUEST_GET_VERSION = 'requestGetVersion';
18 | export const REQUEST_VERSION_UPDATE = 'requestVersionUpdate';
19 | export const AUTHENTICATE = 'authenticate';
20 |
21 | export const SET_PROMPT = 'setPrompt';
22 | export const GET_PROMPT = 'getPrompt';
23 |
--------------------------------------------------------------------------------
/src/messages/NetworkMessage.js:
--------------------------------------------------------------------------------
1 | import Network from '../models/Network'
2 | import * as NetworkMessageTypes from './NetworkMessageTypes';
3 |
4 | export default class NetworkMessage {
5 |
6 | constructor(_type = '', _payload = {}, _resolver = '', _domain = ''){
7 | this.type = _type;
8 | this.payload = _payload;
9 | this.resolver = _resolver;
10 | this.domain = _domain;
11 | }
12 |
13 | static placeholder(){ return new NetworkMessage(); }
14 | static fromJson(json){
15 | let p = Object.assign(this.placeholder(), json);
16 | return p;
17 | }
18 |
19 | static payload(type, payload){
20 | let p = this.placeholder();
21 | p.type = type;
22 | p.payload = payload;
23 | return p;
24 | }
25 |
26 | static signal(type){
27 | let p = this.placeholder();
28 | p.type = type;
29 | return p;
30 | }
31 |
32 | respond(payload){ return new NetworkMessage(this.type, payload, this.resolver); }
33 | error(payload){ return new NetworkMessage(NetworkMessageTypes.ERROR, payload, this.resolver); }
34 | }
--------------------------------------------------------------------------------
/src/messages/NetworkMessageTypes.js:
--------------------------------------------------------------------------------
1 | export const ERROR = 'error';
2 | export const PUSH_SCATTER = 'pushScatter';
3 | export const GET_OR_REQUEST_IDENTITY = 'getOrRequestIdentity';
4 | export const IDENTITY_FROM_PERMISSIONS = 'identityFromPermissions';
5 | export const FORGET_IDENTITY = 'forgetIdentity';
6 | export const REQUEST_SIGNATURE = 'requestSignature';
7 | export const ABI_CACHE = 'abiCache';
8 | export const REQUEST_ARBITRARY_SIGNATURE = 'requestArbitrarySignature';
9 | export const REQUEST_ADD_NETWORK = 'requestAddNetwork';
10 | export const REQUEST_VERSION_UPDATE = 'requestVersionUpdate';
11 | export const AUTHENTICATE = 'authenticate';
--------------------------------------------------------------------------------
/src/messages/PairingTags.js:
--------------------------------------------------------------------------------
1 |
2 | export const INJECTED = 'injected';
3 | export const SCATTER = 'scatter';
--------------------------------------------------------------------------------
/src/migrations/migrator.js:
--------------------------------------------------------------------------------
1 | import * as migrators from './versions/version';
2 |
3 | const mathematicalVersion = version => {
4 | if(version === '0') return 0;
5 | const parts = version.replace(/[.]/g,'_').replace(/[m]/g, '').split('_');
6 | if(parts.length !== 3) throw new Error("Migration error, invalid version");
7 | const zeroed = (x,z) => { let s = x.toString(); while(s.length < z) s += '0'; return s; };
8 | return parseInt(parts.map((x,i) => i === 0 ? zeroed(x,4) : zeroed(x,2)).join(''));
9 | };
10 |
11 | const fnToVersion = fnName => fnName.replace(/[m]/g, '').replace(/[_]/g,'.');
12 |
13 | export default async scatter => {
14 | scatter.meta.regenerateVersion();
15 | if(scatter.isEncrypted()) return false;
16 | if(!scatter.meta.needsUpdating()) return false;
17 |
18 | const lastVersion = mathematicalVersion(scatter.meta.lastVersion);
19 | const nextVersions = Object.keys(migrators).filter(v => mathematicalVersion(v) > lastVersion);
20 | if(nextVersions.length) {
21 | await Promise.all(nextVersions.map(async version => await migrators[version](scatter)));
22 | scatter.meta.lastVersion = fnToVersion(nextVersions[nextVersions.length-1]);
23 | }
24 |
25 | return nextVersions.length > 0;
26 | }
--------------------------------------------------------------------------------
/src/migrations/versions/4.0.1.js:
--------------------------------------------------------------------------------
1 | export const m4_0_1 = scatter => {
2 |
3 | };
--------------------------------------------------------------------------------
/src/migrations/versions/5.0.2.js:
--------------------------------------------------------------------------------
1 | import PluginRepository from '../../plugins/PluginRepository';
2 | import {Blockchains} from '../../models/Blockchains'
3 |
4 | export const m5_0_2 = async scatter => {
5 | const eos = PluginRepository.plugin(Blockchains.EOS);
6 | const endorsedNetwork = await eos.getEndorsedNetwork();
7 | if(!scatter.settings.networks.find(network => network.host === endorsedNetwork.host))
8 | scatter.settings.networks.push(endorsedNetwork);
9 | };
--------------------------------------------------------------------------------
/src/migrations/versions/5.0.4.js:
--------------------------------------------------------------------------------
1 | import PluginRepository from '../../plugins/PluginRepository';
2 | import {Blockchains} from '../../models/Blockchains'
3 |
4 | export const m5_0_4 = async scatter => {
5 | const endorsedNetworks = [
6 | await PluginRepository.plugin(Blockchains.EOS).getEndorsedNetwork(),
7 | await PluginRepository.plugin(Blockchains.ETH).getEndorsedNetwork()
8 | ];
9 |
10 | scatter.settings.networks.map(network => {
11 | const endorsedNetwork = endorsedNetworks.find(endorsed => endorsed.host === network.host);
12 | if(endorsedNetwork) {
13 | const endorsedNetwork = endorsedNetworks.find(endorsed => endorsed.host === network.host);
14 | network.name = endorsedNetwork.name;
15 | network.port = endorsedNetwork.port;
16 | network.protocol = endorsedNetwork.protocol;
17 | } else {
18 | network.protocol = network.port === 443 ? 'https' : 'http'
19 | }
20 | });
21 | };
--------------------------------------------------------------------------------
/src/migrations/versions/6.0.0.js:
--------------------------------------------------------------------------------
1 | import PluginRepository from '../../plugins/PluginRepository';
2 | import {Blockchains} from '../../models/Blockchains'
3 |
4 | export const m6_0_0 = async scatter => {
5 | scatter.meta.acceptedTerms = false;
6 | return true;
7 | };
--------------------------------------------------------------------------------
/src/migrations/versions/6.0.4.js:
--------------------------------------------------------------------------------
1 | import PluginRepository from '../../plugins/PluginRepository';
2 | import {Blockchains} from '../../models/Blockchains'
3 |
4 | export const m6_0_4 = async scatter => {
5 | const endorsedNetworks = [
6 | await PluginRepository.plugin(Blockchains.EOS).getEndorsedNetwork(),
7 | await PluginRepository.plugin(Blockchains.ETH).getEndorsedNetwork()
8 | ];
9 |
10 | scatter.settings.networks.map(network => {
11 | const endorsedNetwork = endorsedNetworks.find(endorsed => endorsed.host === network.host);
12 | if(endorsedNetwork) {
13 | const endorsedNetwork = endorsedNetworks.find(endorsed => endorsed.host === network.host);
14 | network.port = endorsedNetwork.port;
15 | }
16 | });
17 |
18 | scatter.keychain.identities.map(id => {
19 | const filtered = {};
20 | Object.keys(id.accounts).map(key => {
21 | if(scatter.settings.networks.find(net => net.unique() === key))
22 | filtered[key] = id.accounts[key];
23 | })
24 |
25 | id.accounts = filtered;
26 | });
27 |
28 | return true;
29 | };
--------------------------------------------------------------------------------
/src/migrations/versions/version.js:
--------------------------------------------------------------------------------
1 | export * from './4.0.1';
2 | export * from './5.0.2';
3 | export * from './5.0.4';
4 | export * from './6.0.0';
5 | export * from './6.0.4';
--------------------------------------------------------------------------------
/src/models/Account.js:
--------------------------------------------------------------------------------
1 | import KeyPair from './KeyPair';
2 | import PluginRepository from '../plugins/PluginRepository';
3 |
4 | export default class Account {
5 | constructor(){
6 | this.keypairUnique = '';
7 | this.publicKey = '';
8 | this.name = '';
9 | this.authority = '';
10 | }
11 |
12 | formatted(){
13 | const blockchain = this.blockchain();
14 | if(!blockchain || !blockchain.length) return this.name;
15 | return PluginRepository.plugin(blockchain).accountFormatter(this);
16 | }
17 |
18 | blockchain(){
19 | return this.keypairUnique.split(':')[0];
20 | }
21 |
22 | static placeholder(){ return new Account(); }
23 | static fromJson(json){ return Object.assign(this.placeholder(), json); }
24 |
25 | static nameIsValid(name){
26 | return /(^[a-z1-5.]{1,11}[a-z1-5]$)|(^[a-z1-5.]{12}[a-j1-5]$)/g.test(name)
27 | }
28 | }
--------------------------------------------------------------------------------
/src/models/Blockchains.js:
--------------------------------------------------------------------------------
1 |
2 | export const Blockchains = {
3 | EOS:'eos',
4 | ETH:'eth'
5 | };
6 |
7 | export const BlockchainsArray =
8 | Object.keys(Blockchains).map(key => ({key, value:Blockchains[key]}));
--------------------------------------------------------------------------------
/src/models/KeyPair.js:
--------------------------------------------------------------------------------
1 | import AES from 'aes-oop';
2 |
3 | import {Blockchains} from './Blockchains';
4 |
5 | export default class KeyPair {
6 |
7 | constructor(){
8 | this.blockchain = Blockchains.EOS;
9 | this.name = '';
10 | this.privateKey = '';
11 | this.publicKey = '';
12 | }
13 |
14 | static placeholder(){ return new KeyPair(); }
15 | static fromJson(json){ return Object.assign(this.placeholder(), json); }
16 |
17 | unique(){ return `${this.blockchain}:${this.publicKey.toLowerCase()}`; }
18 |
19 | static blockchain(publicKey){
20 | if(publicKey.indexOf('EOS') !== -1) return Blockchains.EOS;
21 | if(publicKey.indexOf('0x') !== -1 && publicKey.length === 42) return Blockchains.ETH;
22 | return null;
23 | }
24 |
25 | /***
26 | * Checks whether a private key is encrypted
27 | * @returns {boolean}
28 | */
29 | isEncrypted(){ switch(this.blockchain) {
30 | // EOS private keys are 51 chars long
31 | case Blockchains.EOS: return this.privateKey.length > 51;
32 | // ETH private keys are 64 chars long
33 | case Blockchains.ETH: return this.privateKey.length > 64;
34 | }}
35 |
36 | /***
37 | * Encrypts this KeyPair's Private Key
38 | * @param seed - The seed to encrypt with
39 | */
40 | encrypt(seed){
41 | if(!this.isEncrypted())
42 | this.privateKey = AES.encrypt(this.privateKey, seed);
43 | }
44 |
45 | /***
46 | * Decrypts this KeyPair's Private Key
47 | * @param seed - The seed to decrypt with
48 | */
49 | decrypt(seed){
50 | if(this.isEncrypted())
51 | this.privateKey = AES.decrypt(this.privateKey, seed);
52 | }
53 | }
--------------------------------------------------------------------------------
/src/models/Keychain.js:
--------------------------------------------------------------------------------
1 | import Identity from './Identity';
2 | import Permission from './Permission';
3 | import KeyPair from './KeyPair';
4 | import IdGenerator from '../util/IdGenerator';
5 | import ObjectHelpers from '../util/ObjectHelpers';
6 | import AES from 'aes-oop';
7 |
8 | export default class Keychain {
9 |
10 | constructor(){
11 | this.keypairs = [];
12 | this.identities = [];
13 | this.permissions = [];
14 | }
15 |
16 | static placeholder(){ return new Keychain(); }
17 | static fromJson(json){
18 | let p = Object.assign(this.placeholder(), json);
19 | if(json.hasOwnProperty('keypairs')) p.keypairs = json.keypairs.map(x => KeyPair.fromJson(x));
20 | if(json.hasOwnProperty('identities')) p.identities = json.identities.map(x => Identity.fromJson(x));
21 | if(json.hasOwnProperty('permissions')) p.permissions = json.permissions.map(x => Permission.fromJson(x));
22 | return p;
23 | }
24 |
25 | clone(){ return Keychain.fromJson(JSON.parse(JSON.stringify(this))) }
26 |
27 | removePermissionsByKeypair(keypair){ this.permissions = this.permissions.filter(perm => perm.keypair !== keypair.unique()); }
28 | removePermission(permission){ this.permissions = this.permissions.filter(perm => perm.checksum !== permission.checksum); }
29 | getPermission(checksum){ return this.permissions.find(permission => permission.checksum === checksum); }
30 | hasPermission(checksum, fields = []){
31 | const fieldKeys = () => Array.isArray(fields) ? fields : Object.keys(fields);
32 |
33 | const permission = this.getPermission(checksum);
34 | console.log('checksum', checksum, permission);
35 | if(!permission) return false;
36 |
37 | // If no fields are supplied but permission exists | valid.
38 | if(fields === null || !fieldKeys().length) return true;
39 |
40 | let fieldsCloneA = Object.assign({}, fields);
41 | let fieldsCloneB = Object.assign({}, permission.fields);
42 | permission.mutableFields.map(field => {
43 | delete fieldsCloneA[field];
44 | delete fieldsCloneB[field];
45 | });
46 |
47 | return ObjectHelpers.deepEqual(fieldsCloneA, fieldsCloneB);
48 |
49 | }
50 |
51 | findIdentity(publicKey){ return this.identities.find(id => id.publicKey === publicKey); }
52 | findIdentityFromDomain(domain){
53 | const idFromPermissions = this.permissions.find(permission => permission.isIdentityOnly() && permission.domain === domain);
54 | if(idFromPermissions) return this.findIdentity(idFromPermissions.identity);
55 | else return null;
56 | }
57 | updateOrPushIdentity(identity){
58 | this.identities.find(id => id.publicKey === identity.publicKey)
59 | ? this.identities = this.identities.map(id => id.publicKey === identity.publicKey ? identity : id)
60 | : this.identities.unshift(identity);
61 | }
62 |
63 | findAccountsWithPublicKey(publicKey){
64 | return this.identities.map(id => id.getAccountFromPublicKey(publicKey)).filter(acc => !!acc);
65 | }
66 |
67 | forBackup(){
68 | const clone = this.clone();
69 | clone.keypairs = [];
70 | clone.permissions = [];
71 | return clone;
72 |
73 | }
74 |
75 | getKeyPair(keypair){
76 | return this.getKeyPairByPublicKey(keypair.publicKey);
77 | // return this.keypairs.find(key => key.publicKey.toLowerCase() === keypair.publicKey.toLowerCase())
78 | }
79 |
80 | getKeyPairByName(name){
81 | return this.keypairs.find(key => key.name.toLowerCase() === name.toLowerCase())
82 | }
83 |
84 | getKeyPairByPublicKey(publicKey){
85 | return this.keypairs.find(key => key.publicKey.toLowerCase() === publicKey.toLowerCase())
86 | }
87 |
88 | removeKeyPair(keypair){
89 | this.keypairs = this.keypairs.filter(key => key.unique() !== keypair.unique());
90 | }
91 | }
--------------------------------------------------------------------------------
/src/models/Meta.js:
--------------------------------------------------------------------------------
1 | import {apis} from '../util/BrowserApis';
2 |
3 | const extension = (apis && apis.app && typeof apis.app.getDetails === 'function') ? apis.app.getDetails() : {};
4 | export default class Meta {
5 |
6 | constructor(){
7 | this.version = extension.version || '';
8 | this.extensionId = extension.id || '';
9 | this.lastVersion = '0';
10 | this.acceptedTerms = false;
11 | }
12 |
13 | regenerateVersion(){
14 | this.version = extension.version || '';
15 | }
16 |
17 | needsUpdating(){
18 | return this.version !== this.lastVersion;
19 | }
20 |
21 | static placeholder(){ return new Meta(); }
22 | static fromJson(json){ return Object.assign(this.placeholder(), json); }
23 | }
--------------------------------------------------------------------------------
/src/models/Network.js:
--------------------------------------------------------------------------------
1 | import {Blockchains} from './Blockchains';
2 |
3 | export default class Network {
4 | constructor(_name = '', _protocol = 'https', _host = '', _port = 0, blockchain = Blockchains.EOS, chainId = ''){
5 | this.name = _name;
6 | this.protocol = _protocol;
7 | this.host = _host;
8 | this.port = _port;
9 | this.blockchain = blockchain;
10 | this.chainId = chainId.toString();
11 | }
12 |
13 | static placeholder(){ return new Network(); }
14 |
15 | static fromJson(json){
16 | const p = Object.assign(Network.placeholder(), json);
17 | p.chainId = p.chainId ? p.chainId.toString() : '';
18 | return p;
19 | }
20 |
21 | static fromUnique(netString){
22 | const blockchain = netString.split(':')[0];
23 | if(netString.indexOf(':chain:') > -1)
24 | return new Network('', '', '','',blockchain, netString.replace(`${blockchain}:chain:`,''));
25 |
26 | const splits = netString.replace(`${blockchain}:`, '').split(':');
27 | return new Network('', '', splits[0], parseInt(splits[1] || 80), blockchain)
28 | }
29 |
30 | unique(){ return (`${this.blockchain}:` + (this.chainId.length ? `chain:${this.chainId}` : `${this.host}:${this.port}`)).toLowerCase(); }
31 | hostport(){ return `${this.host}${this.port ? ':' : ''}${this.port}` }
32 | fullhost(){ return `${this.protocol}://${this.host}${this.port ? ':' : ''}${this.port}` }
33 | clone(){ return Network.fromJson(JSON.parse(JSON.stringify(this))) }
34 | isEmpty(){ return !this.host.length; }
35 | isValid(){ return (this.host.length && this.port) || this.chainId.length }
36 | }
--------------------------------------------------------------------------------
/src/models/Permission.js:
--------------------------------------------------------------------------------
1 | import Network from './Network';
2 | import Identity from './Identity';
3 |
4 | export default class Permission {
5 |
6 | constructor(){
7 | // Mandatory
8 | this.domain = '';
9 | this.network = '';
10 | this.identity = '';
11 | this.keypair = '';
12 |
13 | // Optional
14 | this.contract = null;
15 | this.action = null;
16 | this.checksum = null;
17 |
18 | this.timestamp = 0;
19 |
20 | this.fields = [];
21 | this.mutableFields = [];
22 | }
23 |
24 | static placeholder(){ return new Permission(); }
25 | static fromJson(json){
26 | let p = Object.assign(this.placeholder(), json);
27 | if(json.hasOwnProperty('network')) p.network = Network.fromJson(json.network);
28 | return p;
29 | }
30 |
31 | getIdentity(keychain){
32 | return keychain.findIdentity(this.identity);
33 | }
34 |
35 | isIdentityOnly(){
36 | return !this.contract && !this.action
37 | }
38 |
39 | isContractAction(){
40 | return !this.isIdentityOnly() && this.contract.length && this.action.length
41 | }
42 |
43 | isIdentityFor(domain){
44 | return this.isIdentityOnly() && this.domain === domain;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/models/Scatter.js:
--------------------------------------------------------------------------------
1 | import Meta from './Meta';
2 | import Keychain from './Keychain';
3 | import Settings from './Settings';
4 | import AES from 'aes-oop';
5 | import Hasher from '../util/Hasher'
6 | import IdGenerator from '../util/IdGenerator'
7 |
8 | export default class Scatter {
9 |
10 | constructor(){
11 | this.meta = Meta.placeholder();
12 | this.keychain = Keychain.placeholder();
13 | this.settings = Settings.placeholder();
14 | this.histories = [];
15 | this.hash = Hasher.insecureHash(IdGenerator.text(2048));
16 | }
17 |
18 | static placeholder(){ return new Scatter(); }
19 | static fromJson(json){
20 | let p = Object.assign(this.placeholder(), json);
21 | if(json.hasOwnProperty('meta')) p.meta = Meta.fromJson(json.meta);
22 | if(json.hasOwnProperty('settings')) p.settings = Settings.fromJson(json.settings);
23 | if(json.hasOwnProperty('keychain'))
24 | p.keychain = (typeof json.keychain === 'string')
25 | ? json.keychain : Keychain.fromJson(json.keychain);
26 |
27 | return p;
28 | }
29 |
30 | clone(){ return Scatter.fromJson(JSON.parse(JSON.stringify(this))) }
31 |
32 | isEncrypted(){
33 | return typeof this.keychain !== 'object'
34 | }
35 |
36 | /***
37 | * Encrypts the entire keychain
38 | * @param seed - The seed to encrypt with
39 | */
40 | decrypt(seed){
41 | if(this.isEncrypted()) this.keychain = Keychain.fromJson(AES.decrypt(this.keychain, seed));
42 | }
43 |
44 | /***
45 | * Decrypts the entire keychain
46 | * @param seed - The seed to decrypt with
47 | */
48 | encrypt(seed){
49 | if(!this.isEncrypted()) this.keychain = AES.encrypt(this.keychain, seed);
50 | }
51 |
52 | forBackup(){
53 | const clone = this.clone();
54 | clone.histories = [];
55 | return clone;
56 | }
57 | }
--------------------------------------------------------------------------------
/src/models/Settings.js:
--------------------------------------------------------------------------------
1 | import Network from './Network';
2 | import TimingHelpers from '../util/TimingHelpers';
3 | import {LANG} from '../localization/locales';
4 |
5 | export default class Settings {
6 |
7 | constructor(){
8 | this.networks = [];
9 | this.hasEncryptionKey = false;
10 | this.inactivityInterval = 0;
11 | this.language = 'ENGLISH';
12 | }
13 |
14 | static placeholder(){ return new Settings(); }
15 | static fromJson(json){
16 | let p = Object.assign(this.placeholder(), json);
17 | if(json.hasOwnProperty('networks')) p.networks = json.networks.map(x => Network.fromJson(x));
18 | return p;
19 | }
20 | }
--------------------------------------------------------------------------------
/src/models/alerts/AlertTypes.js:
--------------------------------------------------------------------------------
1 | export const Error = 'error';
2 | export const Prompt = 'prompt';
3 |
4 | export const NamedAccount = 'named_account';
5 | export const SelectAccount = 'select_authority';
6 | export const ClaimIdentity = 'claim_identity';
7 |
8 |
--------------------------------------------------------------------------------
/src/models/errors/Error.js:
--------------------------------------------------------------------------------
1 | import * as ErrorTypes from './ErrorTypes'
2 |
3 | export const ErrorCodes = {
4 | NO_SIGNATURE:402,
5 | FORBIDDEN:403,
6 | TIMED_OUT:408,
7 | LOCKED:423,
8 | UPGRADE_REQUIRED:426,
9 | TOO_MANY_REQUESTS:429
10 | };
11 |
12 | export default class Error {
13 |
14 | constructor(_type, _message, _code = ErrorCodes.LOCKED){
15 | this.type = _type;
16 | this.message = _message;
17 | this.code = _code;
18 | this.isError = true;
19 | }
20 |
21 | static locked(){
22 | return new Error(ErrorTypes.LOCKED, "The user's Scatter is locked. They have been notified and should unlock before continuing.")
23 | }
24 |
25 | static promptClosedWithoutAction(){
26 | return new Error(ErrorTypes.PROMPT_CLOSED, "The user closed the prompt without any action.", ErrorCodes.TIMED_OUT)
27 | }
28 |
29 | static maliciousEvent(){
30 | return new Error(ErrorTypes.MALICIOUS, "Malicious event discarded.", ErrorCodes.FORBIDDEN)
31 | }
32 |
33 | static signatureError(_type, _message){
34 | return new Error(_type, _message, ErrorCodes.NO_SIGNATURE)
35 | }
36 |
37 | static requiresUpgrade(){
38 | return new Error(ErrorTypes.UPGRADE_REQUIRED, "The required version is newer than the User's Scatter", ErrorCodes.UPGRADE_REQUIRED)
39 | }
40 |
41 | static identityMissing(){
42 | return this.signatureError("identity_missing", "Identity no longer exists on the user's keychain");
43 | }
44 |
45 | static signatureAccountMissing(){
46 | return this.signatureError("account_missing", "Missing required accounts, repull the identity");
47 | }
48 |
49 | static malformedRequiredFields(){
50 | return this.signatureError("malformed_requirements", "The requiredFields you passed in were malformed");
51 | }
52 |
53 | static noNetwork(){
54 | return this.signatureError("no_network", "You must bind a network first");
55 | }
56 |
57 | static usedKeyProvider(){
58 | return new Error(
59 | ErrorTypes.MALICIOUS,
60 | "Do not use a `keyProvider` with a Scatter. Use a `signProvider` and return only signatures to this object. A malicious person could retrieve your keys otherwise.",
61 | ErrorCodes.NO_SIGNATURE
62 | )
63 | }
64 |
65 | }
--------------------------------------------------------------------------------
/src/models/errors/ErrorTypes.js:
--------------------------------------------------------------------------------
1 | export const MALICIOUS = 'malicious';
2 | export const LOCKED = 'locked';
3 | export const PROMPT_CLOSED = 'prompt_closed';
4 | export const UPGRADE_REQUIRED = 'upgrade_required';
5 |
--------------------------------------------------------------------------------
/src/models/histories/HistoricEvent.js:
--------------------------------------------------------------------------------
1 |
2 | export default class HistoricEvent {
3 |
4 | constructor(_type = '', _data = null){
5 | this.type = _type;
6 | this.data = _data;
7 | this.timestamp = + new Date();
8 | }
9 |
10 | static placeholder(){ return new HistoricEvent(); }
11 | static fromJson(json){ return Object.assign(this.placeholder(), json); }
12 | }
--------------------------------------------------------------------------------
/src/models/histories/HistoricEventTypes.js:
--------------------------------------------------------------------------------
1 | export const PROVIDED_IDENTITY = "Provided Identity";
2 | export const SIGNED_TRANSACTION = "Signed Transaction";
--------------------------------------------------------------------------------
/src/models/prompts/Prompt.js:
--------------------------------------------------------------------------------
1 | import {RouteNames, promptPrefix} from '../../vue/Routing'
2 | import * as PromptTypes from './PromptTypes'
3 |
4 | export default class Prompt {
5 |
6 | constructor(_type = '', _domain = '', _network = null, _data = {}, _responder = null){
7 | this.type = _type;
8 | this.domain = _domain;
9 | this.network = _network;
10 | this.data = _data;
11 | this.responder = _responder;
12 | }
13 |
14 | static placeholder(){ return new Prompt(); }
15 | static fromJson(json){ return Object.assign(this.placeholder(), json); }
16 |
17 | routeName(){
18 | return `${promptPrefix}${this.type}`;
19 | }
20 |
21 | static scatterIsLocked(){
22 | return new Prompt(PromptTypes.REQUEST_UNLOCK, '', {host:'',port:0}, {}, function(){});
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/src/models/prompts/PromptTypes.js:
--------------------------------------------------------------------------------
1 | export const REQUEST_IDENTITY = 'requestIdentity';
2 | export const REQUEST_SIGNATURE = 'requestSignature';
3 | export const REQUEST_ARBITRARY_SIGNATURE = 'signatureArbitrary';
4 | export const REQUEST_ADD_NETWORK = 'requestAddNetwork';
5 | export const REQUEST_UNLOCK = 'requestUnlock';
6 | export const UPDATE_VERSION = 'updateVersion';
--------------------------------------------------------------------------------
/src/plugins/Plugin.js:
--------------------------------------------------------------------------------
1 |
2 | export default class Plugin {
3 |
4 | constructor(_name = '', _type = ''){
5 | this.name = _name;
6 | this.type = _type;
7 | }
8 |
9 | }
--------------------------------------------------------------------------------
/src/plugins/PluginRepository.js:
--------------------------------------------------------------------------------
1 | import * as PluginTypes from './PluginTypes';
2 | import EOS from './defaults/eos';
3 | import ETH from './defaults/eth';
4 |
5 | /***
6 | * Setting up for plugin based generators,
7 | * this will add more blockchain compatibility in the future.
8 | */
9 |
10 | class PluginRepositorySingleton {
11 |
12 | constructor(){
13 | this.plugins = [];
14 | this.loadPlugins();
15 | }
16 |
17 | loadPlugins(){
18 | this.plugins.push(new EOS());
19 | this.plugins.push(new ETH());
20 | }
21 |
22 | signatureProviders(){
23 | return this.plugins.filter(plugin => plugin.type === PluginTypes.BLOCKCHAIN_SUPPORT);
24 | }
25 |
26 | supportedBlockchains(){
27 | return this.signatureProviders().map(plugin => name)
28 | }
29 |
30 | plugin(name){
31 | return this.plugins.find(plugin => plugin.name === name);
32 | }
33 |
34 | async endorsedNetworks(){
35 | return await Promise.all(this.signatureProviders().map(async plugin => await plugin.getEndorsedNetwork()));
36 | }
37 | }
38 |
39 | const PluginRepository = new PluginRepositorySingleton();
40 | export default PluginRepository;
--------------------------------------------------------------------------------
/src/plugins/PluginTypes.js:
--------------------------------------------------------------------------------
1 | export const BLOCKCHAIN_SUPPORT = 'blockchain_support';
--------------------------------------------------------------------------------
/src/popup.js:
--------------------------------------------------------------------------------
1 | import VueInitializer from './vue/VueInitializer';
2 | import {Routing} from './vue/Routing';
3 | import * as Actions from './store/constants'
4 | import {RouteNames} from './vue/Routing'
5 |
6 | import NavbarComponent from './components/NavbarComponent.vue'
7 | import InputComponent from './components/InputComponent.vue'
8 | import ButtonComponent from './components/ButtonComponent.vue'
9 | import SearchComponent from './components/SearchComponent.vue'
10 | import SelectComponent from './components/SelectComponent.vue'
11 | import NavActionsComponent from './components/NavActionsComponent.vue'
12 | import ViewBase from './views/ViewBase.vue'
13 | import Alert from './components/alerts/Alert.vue'
14 |
15 | class Popup {
16 |
17 | constructor(){
18 | const components = [
19 | {tag:'navbar', vue:NavbarComponent},
20 | {tag:'nav-actions', vue:NavActionsComponent},
21 | {tag:'cin', vue:InputComponent},
22 | {tag:'btn', vue:ButtonComponent},
23 | {tag:'search', vue:SearchComponent},
24 | {tag:'sel', vue:SelectComponent},
25 | {tag:'alert', vue:Alert},
26 | {tag:'view-base', vue:ViewBase},
27 | ];
28 |
29 | const routes = Routing.routes();
30 |
31 | const middleware = (to, next, store) => {
32 | if(Routing.isRestricted(to.name))
33 | store.dispatch(Actions.IS_UNLOCKED)
34 | .then(unlocked => (unlocked) ? next() : next({name:RouteNames.ENTRY}));
35 | else next();
36 | };
37 |
38 | new VueInitializer(routes, components, middleware, (router, store) => {
39 | store.dispatch(Actions.IS_UNLOCKED)
40 | .then(unlocked => {
41 | if(!store.getters.meta.hasOwnProperty('acceptedTerms') || !store.getters.meta.acceptedTerms)
42 | router.push({name: RouteNames.ONBOARDING});
43 | else router.push({name: RouteNames.MAIN_MENU});
44 | });
45 | });
46 | }
47 |
48 | }
49 |
50 | const popup = new Popup();
51 |
--------------------------------------------------------------------------------
/src/prompt.js:
--------------------------------------------------------------------------------
1 | import VueInitializer from './vue/VueInitializer';
2 | import {Routing} from './vue/Routing';
3 | import * as Actions from './store/constants'
4 | import {RouteNames} from './vue/Routing'
5 | import Prompt from './models/prompts/Prompt'
6 | import PromptBase from './prompts/PromptBase.vue'
7 | import Network from './models/Network'
8 |
9 | import * as PromptTypes from './models/prompts/PromptTypes'
10 | import Alert from './components/alerts/Alert.vue'
11 | import ButtonComponent from './components/ButtonComponent.vue'
12 | import SearchComponent from './components/SearchComponent.vue'
13 | import InputComponent from './components/InputComponent.vue'
14 | import SelectComponent from './components/SelectComponent.vue'
15 | import KeyValue from './components/KeyValue.vue'
16 | import InternalMessage from './messages/InternalMessage'
17 | import * as InternalMessageTypes from './messages/InternalMessageTypes'
18 | import {apis} from './util/BrowserApis';
19 |
20 | class PromptWindow {
21 |
22 | constructor(){
23 | let prompt = window.data || apis.extension.getBackgroundPage().notification || null;
24 |
25 | // TODO: Pair prompt with a checksum from the state store so that
26 | // even if an attacker manages to open a clickjack/malicious prompt
27 | // we will be able to identify and reject it.
28 |
29 | // TODO: Error handling for prompt messages
30 |
31 | prompt = Prompt.fromJson(prompt);
32 |
33 | const components = [
34 | {tag:'prompt-base', vue:PromptBase},
35 | {tag:'btn', vue:ButtonComponent},
36 | {tag:'search', vue:SearchComponent},
37 | {tag:'cin', vue:InputComponent},
38 | {tag:'sel', vue:SelectComponent},
39 | {tag:'alert', vue:Alert},
40 | {tag:'key-value', vue:KeyValue},
41 | ];
42 |
43 | const routes = Routing.routes(true);
44 | const middleware = (to, next, store) => next();
45 |
46 | new VueInitializer(routes, components, middleware, (router, store) => {
47 | store.dispatch(Actions.PUSH_PROMPT, prompt);
48 | router.push({name:prompt.routeName()});
49 | });
50 | }
51 |
52 | }
53 |
54 | const popup = new PromptWindow();
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/prompts/PromptBase.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{flipWords(camelToTitle(prompt.type))}}
6 |
7 | {{prompt.domain}}
8 | {{prompt.network.host}}
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
49 |
50 |
--------------------------------------------------------------------------------
/src/prompts/RequestAddNetwork.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{prompt.domain}} {{locale(langKeys.REQUEST_AddNetwork)[0]}}
15 |
16 |
17 |
18 | {{locale(langKeys.GENERIC_Host)}}
19 | {{prompt.network.host}}
20 |
21 |
22 |
23 | {{locale(langKeys.GENERIC_Port)}}
24 | {{prompt.network.port}}
25 |
26 |
27 |
28 |
29 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
71 |
72 |
--------------------------------------------------------------------------------
/src/prompts/RequestArbitrarySignaturePrompt.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
22 |
23 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
89 |
90 |
--------------------------------------------------------------------------------
/src/prompts/RequestUnlock.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
19 |
20 |
21 |
22 |
23 |
44 |
45 |
--------------------------------------------------------------------------------
/src/prompts/UpdateVersion.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
20 |
21 |
22 |
23 |
24 |
44 |
45 |
--------------------------------------------------------------------------------
/src/services/AccountService.js:
--------------------------------------------------------------------------------
1 | import Account from '../models/Account'
2 | import PluginRepository from '../plugins/PluginRepository'
3 |
4 | // TODO: Only dependence on eosjs
5 | import * as Eos from 'eosjs';
6 |
7 | export default class AccountService {
8 |
9 | /***
10 | * Tries to import an account using an existing private key
11 | * @param keypair
12 | * @param network
13 | * @param context
14 | * @returns {Promise}
15 | */
16 | static importFromKey(keypair, network, context){
17 | return new Promise((resolve, reject) => {
18 | const accountSelected = (account) => resolve({keypair, account});
19 |
20 | // Accounts for this blockchain need importation
21 | if(PluginRepository.plugin(network.blockchain).accountsAreImported())
22 | PluginRepository.plugin(network.blockchain).importAccount(keypair, network, context, accountSelected);
23 |
24 | // Accounts for this blockchain are freebased.
25 | else accountSelected(Account.fromJson({
26 | name:keypair.name,
27 | authority:'',
28 | publicKey:keypair.publicKey,
29 | keypairUnique:keypair.unique()
30 | }))
31 |
32 |
33 | })
34 | }
35 | }
--------------------------------------------------------------------------------
/src/services/AuthenticationService.js:
--------------------------------------------------------------------------------
1 | import * as Actions from '../store/constants';
2 | import {RouteNames} from '../vue/Routing'
3 | import AlertMsg from '../models/alerts/AlertMsg'
4 | import Mnemonic from '../util/Mnemonic'
5 | import Hasher from '../util/Hasher';
6 | import IdGenerator from '../util/IdGenerator'
7 | import StorageService from './StorageService';
8 |
9 | export default class AuthenticationService {
10 |
11 | /***
12 | * Verifies if the password provided matches the decryption seed
13 | * @param password
14 | * @param context
15 | */
16 | static verifyPassword(password, context){
17 | return new Promise((resolve, reject) => {
18 | const sendToEntry = () => {
19 | // context[Actions.PUSH_ALERT](AlertMsg.WrongPassword());
20 | context[Actions.PUSH_ALERT](AlertMsg.WrongPassword());
21 | context.$router.push({name:RouteNames.ENTRY});
22 | reject();
23 | };
24 |
25 | // We're going to immediately take a wrong password here as a sign
26 | // of hostility and lock Scatter by overwriting the seed.
27 | context[Actions.SET_SEED](password).then(() => {
28 | context[Actions.IS_UNLOCKED]().then(unlocked => {
29 | if(!unlocked) { sendToEntry(); return false; }
30 | resolve();
31 | }).catch(() => sendToEntry())
32 | }).catch(() => sendToEntry());
33 | })
34 | }
35 |
36 | /***
37 | * Changes a user's password.
38 | * --------------------------
39 | * !!IMPORTANT!!
40 | * Should only be done after verification of the
41 | * old password or it will corrupt all private keys.
42 | * --------------------------
43 | * @param oldPassword
44 | * @param newPassword
45 | * @param context
46 | */
47 | static async changePassword(oldPassword, newPassword, context){
48 | const [oldMnemonic, oldSeed] = await Mnemonic.generateMnemonic(oldPassword);
49 |
50 | // Setting a new salt every time the password is changed.
51 | await StorageService.setSalt(Hasher.insecureHash(IdGenerator.text(32)));
52 |
53 | const [newMnemonic, newSeed] = await Mnemonic.generateMnemonic(newPassword);
54 |
55 | const scatter = context.scatter.clone();
56 | context[Actions.SET_MNEMONIC](newMnemonic);
57 | context[Actions.SET_SEED](newPassword).then(() => {
58 | // Replacing private key encryption
59 | scatter.keychain.keypairs.map(keypair => keypair.decrypt(oldSeed));
60 |
61 | context[Actions.UPDATE_STORED_SCATTER](scatter).then(() => {
62 | context.$router.push({name:RouteNames.SHOW_MNEMONIC});
63 | })
64 | });
65 | }
66 |
67 | /***
68 | * Checks if a password is valid
69 | * @param password
70 | * @param confirmation
71 | * @param context
72 | * @returns {boolean}
73 | */
74 | static validPassword(password, confirmation, context){
75 | if(password.length < 8){
76 | context[Actions.PUSH_ALERT](AlertMsg.BadPassword());
77 | return false;
78 | }
79 |
80 | if(password !== confirmation){
81 | context[Actions.PUSH_ALERT](AlertMsg.PasswordsDoNotMatch());
82 | return false;
83 | }
84 |
85 | return true;
86 | }
87 |
88 | }
--------------------------------------------------------------------------------
/src/services/IdentityService.js:
--------------------------------------------------------------------------------
1 | import Identity from '../models/Identity'
2 | import Prompt from '../models/prompts/Prompt';
3 | import * as PromptTypes from '../models/prompts/PromptTypes'
4 | import NotificationService from '../services/NotificationService'
5 |
6 | export default class IdentityService {
7 |
8 | //TODO Mock
9 | static register(name, scatter){
10 | return new Promise((resolve, reject) => {
11 | resolve(true)
12 | })
13 | }
14 |
15 | //TODO Mock
16 | static nameExists(name, scatter){
17 | return new Promise((resolve, reject) => {
18 | // Check if exists within another scatter
19 | resolve(false);
20 | })
21 | }
22 |
23 | static identityPermission(domain, scatter){
24 | return scatter.keychain.permissions.find(perm => perm.isIdentityFor(domain));
25 | }
26 |
27 | static identityFromPermissionsOrNull(domain, scatter){
28 | const identityFromPermission = IdentityService.identityPermission(domain, scatter);
29 | return identityFromPermission ? identityFromPermission.getIdentity(scatter.keychain) : null;
30 | }
31 |
32 | static getOrRequestIdentity(domain, fields, scatter, callback){
33 |
34 | // Possibly getting an Identity that has been synced with this application.
35 | const identityFromPermission = IdentityService.identityFromPermissionsOrNull(domain, scatter);
36 | let identity = identityFromPermission;
37 |
38 | const sendBackIdentity = id => {
39 | if(!id || id.hasOwnProperty('isError')){
40 | callback(null, null);
41 | return false;
42 | }
43 |
44 | callback(id.asOnlyRequiredFields(fields), !!identityFromPermission);
45 | };
46 |
47 | if(identity){
48 | // Even though there is a previous permission,
49 | // the identity might have changed and no longer
50 | // meets the requirements.
51 | if(identity.hasRequiredFields(fields)){
52 | sendBackIdentity(identity);
53 | return false;
54 | } else {
55 | // TODO: Remove permission
56 | }
57 | }
58 | else NotificationService.open(new Prompt(PromptTypes.REQUEST_IDENTITY, domain, null, fields, sendBackIdentity));
59 | }
60 | }
--------------------------------------------------------------------------------
/src/services/KeyPairService.js:
--------------------------------------------------------------------------------
1 | import {BlockchainsArray, Blockchains} from '../models/Blockchains';
2 | import PluginRepository from '../plugins/PluginRepository'
3 | import AlertMsg from '../models/alerts/AlertMsg'
4 | import * as Actions from '../store/constants';
5 |
6 | export default class KeyPairService {
7 |
8 | /***
9 | * Tries to make a keypair in place from a private key
10 | * @param keypair
11 | * @returns {Promise.}
12 | */
13 | static async makePublicKey(keypair){
14 | return new Promise((resolve) => {
15 | setTimeout(() => {
16 | if(keypair.privateKey.length < 50) {
17 | resolve(false);
18 | return false;
19 | }
20 |
21 | let publicKey = '';
22 |
23 | BlockchainsArray.map(blockchainKV => {
24 | try {
25 | if(!publicKey.length) {
26 | const blockchain = blockchainKV.value;
27 |
28 | const plugin = PluginRepository.plugin(blockchain);
29 | if (plugin && plugin.validPrivateKey(keypair.privateKey)) {
30 | publicKey = plugin.privateToPublic(keypair.privateKey);
31 | keypair.blockchain = blockchain;
32 | }
33 | }
34 | } catch(e){}
35 | });
36 |
37 | if(publicKey) keypair.publicKey = publicKey;
38 | resolve(true);
39 | },100)
40 | })
41 | }
42 |
43 | static async generateKeyPair(keypair){
44 | const plugin = PluginRepository.plugin(keypair.blockchain);
45 | if(!plugin) return false;
46 |
47 | plugin.randomPrivateKey().then(privateKey => {
48 | const publicKey = plugin.privateToPublic(privateKey);
49 | if(plugin.validPublicKey(publicKey) && plugin.validPrivateKey(privateKey)){
50 | keypair.publicKey = publicKey;
51 | keypair.privateKey = privateKey;
52 | }
53 | });
54 |
55 | return true;
56 | }
57 |
58 | static saveKeyPair(keypair, context, callback){
59 | if(!keypair.name.length) return context[Actions.PUSH_ALERT](AlertMsg.BadKeyPairName());
60 |
61 | const scatter = context.scatter.clone();
62 | if(scatter.keychain.getKeyPair(keypair))
63 | return context[Actions.PUSH_ALERT](AlertMsg.KeyPairExists());
64 | if(scatter.keychain.getKeyPairByName(keypair.name))
65 | return context[Actions.PUSH_ALERT](AlertMsg.KeyPairExists());
66 |
67 | scatter.keychain.keypairs.push(keypair);
68 | context[Actions.UPDATE_STORED_SCATTER](scatter).then(() => callback());
69 | }
70 |
71 | }
--------------------------------------------------------------------------------
/src/services/NotificationService.js:
--------------------------------------------------------------------------------
1 | import Error from '../models/errors/Error'
2 | import {apis} from '../util/BrowserApis';
3 | import InternalMessage from '../messages/InternalMessage'
4 | import * as InternalMessageTypes from '../messages/InternalMessageTypes'
5 |
6 | let openWindow = null;
7 |
8 | export default class NotificationService {
9 |
10 | /***
11 | * Opens a prompt window outside of the extension
12 | * @param notification
13 | */
14 | static async open(notification){
15 | if(openWindow){
16 | // For now we're just going to close the window to get rid of the error
17 | // that is caused by already open windows swallowing all further requests
18 | openWindow.close();
19 | openWindow = null;
20 |
21 | // Alternatively we could focus the old window, but this would cause
22 | // urgent 1-time messages to be lost, such as after dying in a game and
23 | // uploading a high-score. That message will be lost.
24 | // openWindow.focus();
25 | // return false;
26 |
27 | // A third option would be to add a queue, but this could cause
28 | // virus-like behavior as apps overflow the queue causing the user
29 | // to have to quit the browser to regain control.
30 | }
31 |
32 |
33 | const height = 600;
34 | const width = 700;
35 | let middleX = window.screen.availWidth/2 - (width/2);
36 | let middleY = window.screen.availHeight/2 - (height/2);
37 |
38 | const getPopup = async () => {
39 | try {
40 | const url = apis.runtime.getURL('/prompt.html');
41 |
42 | // Notifications get bound differently depending on browser
43 | // as Firefox does not support opening windows from background.
44 | if(typeof browser !== 'undefined') {
45 | const created = await apis.windows.create({
46 | url,
47 | height,
48 | width,
49 | type:'popup'
50 | });
51 |
52 | window.notification = notification;
53 | return created;
54 | }
55 | else {
56 | const win = window.open(url, 'ScatterPrompt', `width=${width},height=${height},resizable=0,top=${middleY},left=${middleX},titlebar=0`);
57 | win.data = notification;
58 | openWindow = win;
59 | return win;
60 | }
61 | } catch (e) {
62 | console.log('notification error', e);
63 | return null;
64 | }
65 | }
66 |
67 | await InternalMessage.payload(InternalMessageTypes.SET_PROMPT, JSON.stringify(notification)).send();
68 |
69 | let popup = await getPopup();
70 |
71 | // Handles the user closing the popup without taking any action
72 | popup.onbeforeunload = () => {
73 | notification.responder(Error.promptClosedWithoutAction());
74 |
75 | // https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload
76 | // Must return undefined to bypass form protection
77 | openWindow = null;
78 | return undefined;
79 | };
80 |
81 | }
82 |
83 | /***
84 | * Always use this method for closing notification popups.
85 | * Otherwise you will double send responses and one will always be null.
86 | */
87 | static async close(){
88 | if(typeof browser !== 'undefined') {
89 | const {id: windowId,} = (await apis.windows.getCurrent());
90 | apis.windows.remove(windowId);
91 | } else {
92 | window.onbeforeunload = () => {};
93 | window.close();
94 | }
95 | }
96 |
97 | }
--------------------------------------------------------------------------------
/src/services/RIDLService.js:
--------------------------------------------------------------------------------
1 | import ridl from 'ridl';
2 | import * as Actions from '../store/constants';
3 | import AlertMsg from '../models/alerts/AlertMsg'
4 | import PluginRepository from '../plugins/PluginRepository'
5 | import {Blockchains} from '../models/Blockchains'
6 |
7 | const enabled = false;
8 |
9 | setTimeout(() => {
10 | PluginRepository.plugin(Blockchains.EOS)
11 | .getEndorsedNetwork()
12 | .then(network => ridl.setNetwork(network));
13 | }, 50);
14 |
15 | export default class RIDLService {
16 |
17 | constructor(){}
18 |
19 | static async claimIdentity(newName, identity, context){
20 | return new Promise(async(resolve,reject) => {
21 |
22 | if(!newName.length) return reject(null);
23 | const hash = await ridl.identity.getHash(newName);
24 | if(!hash) return reject(context[Actions.PUSH_ALERT](AlertMsg.NoSuchIdentityName()));
25 |
26 | context[Actions.PUSH_ALERT](AlertMsg.ClaimIdentity(newName)).then(async res => {
27 | if(!res || !res.hasOwnProperty('text')) return reject(null);
28 |
29 | if(!PluginRepository.plugin(Blockchains.EOS).validPrivateKey(res.text))
30 | return reject(context[Actions.PUSH_ALERT](AlertMsg.InvalidPrivateKey()));
31 |
32 | const signedHash = ridl.sign(hash, res.text);
33 | delete res.text;
34 |
35 | const claimed = await ridl.identity.claim(newName, signedHash, identity.publicKey);
36 | if(!claimed) return reject(context[Actions.PUSH_ALERT](AlertMsg.NoSuchIdentityName()));
37 |
38 | // Removing now unused randomized RIDL account
39 | if(!await ridl.identity.registered(identity.name)) {
40 | const previousHash = await ridl.identity.getHash(identity.name);
41 | const signedStaleHash = previousHash ? await context[Actions.SIGN_RIDL]({hash:previousHash, publicKey:identity.publicKey}) : false;
42 | if(signedStaleHash) await ridl.identity.release(identity.name, signedStaleHash);
43 | }
44 |
45 | identity.name = newName;
46 | identity.ridl = parseInt(claimed.registered);
47 | resolve(identity);
48 | //5KjbZQLH3EAfgXF3jejYM2WZjzJCUQH7NEkT1mVcBy2xoFdSWro
49 | })
50 |
51 | });
52 |
53 |
54 | }
55 |
56 | static async identify(publicKey){
57 | if(!enabled) return ridl.identity.randomName();
58 | const name = await ridl.identity.randomUniqueName();
59 | if(!await ridl.identity.identify(name, publicKey)) return null;
60 | return name;
61 | }
62 | }
--------------------------------------------------------------------------------
/src/services/StorageService.js:
--------------------------------------------------------------------------------
1 | import Scatter from '../models/Scatter'
2 | import {apis} from '../util/BrowserApis';
3 |
4 | export default class StorageService {
5 |
6 | constructor(){}
7 |
8 | /***
9 | * Saves an instance of Scatter in the extension's local storage
10 | * The keychain will always be encrypted when in storage
11 | * @param scatter
12 | * @returns {Promise}
13 | */
14 | static save(scatter){
15 | return new Promise(resolve => {
16 | apis.storage.local.set({scatter}, () => {
17 | resolve(scatter);
18 | });
19 | })
20 | };
21 |
22 | /***
23 | * Gets an instance of Scatter from the extension's local storage
24 | * Will return a new Scatter instance if not found.
25 | * @returns {Promise}
26 | */
27 | static get() {
28 | return new Promise(resolve => {
29 | apis.storage.local.get('scatter', (possible) => {
30 | (possible && Object.keys(possible).length && possible.hasOwnProperty('scatter'))
31 | ? resolve(Scatter.fromJson(possible.scatter))
32 | : resolve(Scatter.placeholder());
33 | });
34 | })
35 | }
36 |
37 | /***
38 | * Removes the instance of Scatter.
39 | * This will delete all user data.
40 | * @returns {Promise}
41 | */
42 | static remove(){
43 | return new Promise(resolve => {
44 | apis.storage.local.remove('scatter', () => {
45 | resolve();
46 | });
47 | })
48 | }
49 |
50 | /***
51 | * Caches an ABI
52 | * @param contractName
53 | * @param chainId
54 | * @param abi
55 | * @returns {Promise}
56 | */
57 | static cacheABI(contractName, chainId, abi){
58 | return new Promise(resolve => {
59 | apis.storage.local.set({[`abi:${contractName}:${chainId}`]:abi}, () => {
60 | resolve(abi);
61 | });
62 | });
63 | }
64 |
65 | /***
66 | * Fetches an ABI from cache
67 | * @param contractName
68 | * @param chainId
69 | * @returns {Promise}
70 | */
71 | static getABI(contractName, chainId){
72 | return new Promise(resolve => {
73 | const prop = `abi:${contractName}:${chainId}`;
74 | apis.storage.local.get(prop, (possible) => {
75 | if(JSON.stringify(possible) !== '{}') resolve(possible[prop]);
76 | else resolve('no cache');
77 | });
78 | })
79 | }
80 |
81 | static getSalt(){
82 | return new Promise(resolve => {
83 | apis.storage.local.get('salt', (possible) => {
84 | if(JSON.stringify(possible) !== '{}') resolve(possible.salt);
85 | else resolve('SALT_ME');
86 | });
87 | })
88 | }
89 |
90 | static setSalt(salt){
91 | return new Promise(resolve => {
92 | apis.storage.local.set({salt}, () => {
93 | resolve(salt);
94 | });
95 | })
96 | }
97 | }
--------------------------------------------------------------------------------
/src/store/constants.js:
--------------------------------------------------------------------------------
1 |
2 | export const LOAD_SCATTER = 'loadScatter';
3 | export const SET_SCATTER = 'setScatter';
4 | export const IS_UNLOCKED = 'isUnlocked';
5 | export const UPDATE_STORED_SCATTER = 'updateStoredScatter';
6 | export const CREATE_NEW_SCATTER = 'createNewScatter';
7 | export const IMPORT_SCATTER = 'importScatter';
8 | export const SET_MNEMONIC = 'setMnemonic';
9 | export const SET_SEED = 'setSeed';
10 | export const SET_AUTO_LOCK = 'setAutoLock';
11 | export const LOCK = 'lock';
12 | export const DESTROY = 'destroy';
13 | export const SIGN_RIDL = 'signRIDL';
14 |
15 | export const PUSH_ALERT = 'pushAlert';
16 | export const PULL_ALERT = 'pullAlert';
17 | export const PUSH_ALERT_RESULT = 'pushAlertResult';
18 | export const CLEAR_ALERT_RESULT = 'clearAlertResult';
19 |
20 | export const PUSH_PROMPT = 'pushPrompt';
21 | export const PULL_PROMPT = 'pullPrompt';
--------------------------------------------------------------------------------
/src/store/mutations.js:
--------------------------------------------------------------------------------
1 | import * as Mutations from './constants'
2 | import TimingHelpers from '../util/TimingHelpers';
3 |
4 | export const mutations = {
5 | [Mutations.SET_SCATTER]:(state, scatter) => state.scatter = scatter,
6 | [Mutations.SET_MNEMONIC]:(state, mnemonic) => state.mnemonic = mnemonic,
7 | [Mutations.PUSH_ALERT]:(state, error) => state.alerts.push(error),
8 | [Mutations.PULL_ALERT]:(state, error) => state.alerts.shift(),
9 | [Mutations.PUSH_ALERT_RESULT]:(state, alertResult) => state.alertResult = alertResult,
10 | [Mutations.CLEAR_ALERT_RESULT]:(state) => state.alertResult = null,
11 | [Mutations.PUSH_PROMPT]:(state, prompt) => state.prompt = prompt,
12 | [Mutations.SET_AUTO_LOCK]:(state, inactivityInterval) =>
13 | state.scatter.settings.inactivityInterval = TimingHelpers.minutes(inactivityInterval),
14 | };
--------------------------------------------------------------------------------
/src/store/store.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex';
3 |
4 | import {mutations} from './mutations';
5 | import {actions} from './actions';
6 | import * as LANG_KEYS from '../localization/keys';
7 |
8 | import {IdentityRequiredFields} from '../models/Identity'
9 |
10 | Vue.use(Vuex);
11 |
12 | const state = {
13 | scatter:null,
14 | mnemonic:null,
15 |
16 | alerts:[],
17 | alertResult:null,
18 |
19 | prompt:null,
20 | };
21 |
22 | const getters = {
23 | meta:state => state.scatter.meta,
24 | identities:state => state.scatter.keychain.identities,
25 | permissions:state => state.scatter.keychain.permissions,
26 | keypairs:state => state.scatter.keychain.keypairs,
27 | networks:state => state.scatter.settings.networks,
28 | histories:state => state.scatter.histories,
29 | autoLockInterval:state => state.scatter.settings.inactivityInterval,
30 | language:state => state.scatter.settings.language,
31 |
32 | // FOR PROMPTS ONLY
33 | identityFields:state => IdentityRequiredFields.fromJson(state.prompt.data),
34 | requiredFields:state => IdentityRequiredFields.fromJson(state.prompt.data.requiredFields || {}),
35 | messages:state => state.prompt.data.messages || [],
36 | };
37 |
38 | export const store = new Vuex.Store({
39 | state,
40 | getters,
41 | mutations,
42 | actions
43 | })
--------------------------------------------------------------------------------
/src/util/BrowserApis.js:
--------------------------------------------------------------------------------
1 |
2 | const swallow = fn => {try { fn() } catch(e){}};
3 |
4 | class ApiGenerator {
5 | constructor(){
6 | [
7 | 'app',
8 | 'storage',
9 | 'extension',
10 | 'runtime',
11 | 'windows'
12 | ]
13 | .map(api => {
14 | if(typeof chrome !== 'undefined') swallow(() => {if(chrome[api]) this[api] = chrome[api]});
15 | if(typeof browser !== 'undefined') swallow(() => {if(browser[api]) this[api] = browser[api]});
16 | });
17 |
18 | if(typeof browser !== 'undefined') swallow(() => {if (browser && browser.runtime) this.runtime = browser.runtime});
19 | }
20 | }
21 |
22 | export const apis = new ApiGenerator();
--------------------------------------------------------------------------------
/src/util/ContractHelpers.js:
--------------------------------------------------------------------------------
1 | import Hasher from './Hasher'
2 | import ObjectHelpers from './ObjectHelpers'
3 | import {Blockchains} from '../models/Blockchains';
4 |
5 | export default class ContractHelpers {
6 |
7 | static getContractAndActionNames(transaction){
8 | return {contract:transaction.code, action:transaction.type};
9 | }
10 |
11 | static contractActionChecksum(contract, action, domain, network){
12 | return Hasher.insecureHash(JSON.stringify(Object.assign({},
13 | {contract, action, domain, network})
14 | ));
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/src/util/EOSKeygen.js:
--------------------------------------------------------------------------------
1 | import KeyPair from '../models/KeyPair';
2 | import Mnemonic from './Mnemonic';
3 | import {PrivateKey} from 'eosjs-ecc';
4 |
5 | export default class EOSKeygen {
6 |
7 | /***
8 | * Generates a KeyPair
9 | * @returns {KeyPair}
10 | */
11 | static generateKeys(){
12 | let [mnemonic, seed] = Mnemonic.generateDanglingMnemonic();
13 | let privateKey = EOSKeygen.generatePrivateKey(seed);
14 | let publicKey = EOSKeygen.privateToPublic(privateKey);
15 | return KeyPair.fromJson({publicKey, privateKey})
16 | }
17 |
18 | /***
19 | * Generates only a private key
20 | * @param seed - The seed to build the key from
21 | * @returns {wif}
22 | */
23 | static generatePrivateKey(seed) {
24 | return PrivateKey.fromSeed(seed).toWif()
25 | }
26 |
27 | /***
28 | * Converts a private key to a public key
29 | * @param privateKey - The private key to convert
30 | */
31 | static privateToPublic(privateKey) {
32 | return PrivateKey.fromWif(privateKey).toPublic().toString()
33 | }
34 |
35 | /***
36 | * Checks if a private key is a valid EOS private key
37 | * @param privateKey - The private key to check
38 | * @returns {boolean}
39 | */
40 | static validPrivateKey(privateKey){
41 | return PrivateKey.isValid(privateKey);
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/src/util/GenericTools.js:
--------------------------------------------------------------------------------
1 |
2 | export const strippedHost = () => {
3 | let host = location.hostname;
4 |
5 | // Replacing www. only if the domain starts with it.
6 | if(host.indexOf('www.') === 0) host = host.replace('www.', '');
7 |
8 | return host;
9 | };
--------------------------------------------------------------------------------
/src/util/Hasher.js:
--------------------------------------------------------------------------------
1 | const ecc = require('eosjs-ecc');
2 | const scrypt = require('scrypt-async');
3 | import StorageService from '../services/StorageService';
4 |
5 | export default class Hasher {
6 |
7 | /***
8 | * Hashes a cleartext using the SHA-256 algorithm.
9 | * This is INSECURE and should only be used for fingerprinting.
10 | * @param cleartext
11 | */
12 | static insecureHash(cleartext) {
13 | return ecc.sha256(cleartext);
14 | }
15 |
16 | /***
17 | * Hashes a cleartext using scrypt.
18 | * @param cleartext
19 | */
20 | static async secureHash(cleartext) {
21 | return new Promise(async resolve => {
22 | // We don't need a salt here, because this is a non-saved(!) hash,
23 | // which is used to create a seed that is used to encrypt
24 | // the keychain using AES which has it's own salt.
25 | // const salt = this.insecureHash(cleartext) + this.insecureHash(cleartext).slice(0,16);
26 | const salt = await StorageService.getSalt();
27 | scrypt(cleartext, salt, {
28 | N: 16384,
29 | r: 8,
30 | p: 1,
31 | dkLen: 16,
32 | encoding: 'hex'
33 | }, (derivedKey) => {
34 | resolve(derivedKey);
35 | })
36 | });
37 | }
38 | }
--------------------------------------------------------------------------------
/src/util/IdGenerator.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export default class IdGenerator {
4 |
5 | static rand(){
6 | const arr = new Uint32Array(1);
7 | window.crypto.getRandomValues(arr);
8 | return arr[0]/(0xffffffff + 1);
9 | }
10 |
11 | /***
12 | * Generates a random string of specified size
13 | * @param size - The length of the string to generate
14 | * @returns {string} - The generated random string
15 | */
16 | static text(size){
17 | let text = "";
18 | const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
19 | for(let i=0; i max ) return IdGenerator.numeric(max) + IdGenerator.numeric(size - max);
33 |
34 | max = Math.pow(10, size+add);
35 | const min = max / 10,
36 | number = Math.floor(IdGenerator.rand() * (max - min + 1)) + min;
37 |
38 | return ("" + number).substring(add);
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/src/util/Mnemonic.js:
--------------------------------------------------------------------------------
1 | import Hasher from "./Hasher";
2 | import bip39 from 'bip39'
3 |
4 | export class Mnemonic {
5 |
6 | /***
7 | * Generates a mnemonic from a password
8 | * @param password
9 | * @returns {[string,string]}
10 | */
11 | static async generateMnemonic(password) {
12 | const hash = await Hasher.secureHash(password);
13 | let mnemonic = bip39.entropyToMnemonic(hash);
14 | return [mnemonic, bip39.mnemonicToSeedHex(mnemonic)];
15 | }
16 |
17 | static async mnemonicToSeed(mnemonic){
18 | return bip39.mnemonicToSeedHex(mnemonic);
19 | }
20 |
21 | /***
22 | * Generates a random mnemonic
23 | * @returns {[string,string]}
24 | */
25 | static generateDanglingMnemonic() {
26 | let mnemonic = bip39.generateMnemonic();
27 | return [mnemonic, bip39.mnemonicToSeedHex(mnemonic)];
28 | }
29 | }
30 |
31 | export default Mnemonic;
--------------------------------------------------------------------------------
/src/util/ObjectHelpers.js:
--------------------------------------------------------------------------------
1 | /***
2 | * A set of helpers for Objects/Arrays
3 | */
4 | export default class ObjectHelpers {
5 |
6 | /***
7 | * Groups an array by key
8 | * @param array
9 | * @param key
10 | * @returns {*}
11 | */
12 | static groupBy(array, key){
13 | return array.reduce((acc, item) => {
14 | (acc[item[key]] = acc[item[key]] || []).push(item);
15 | return acc;
16 | }, {});
17 | }
18 |
19 | /***
20 | * Makes a single level array distinct
21 | * @param array
22 | * @returns {*}
23 | */
24 | static distinct(array){
25 | return array.reduce((a,b) => (a.includes(b)) ? a : a.concat(b), []);
26 | }
27 |
28 | /***
29 | * Makes an object array distinct ( uses deep checking )
30 | * @param array
31 | * @returns {*}
32 | */
33 | static distinctObjectArray(array){
34 | return array.reduce((a,b) => (!!a.find(x => this.deepEqual(x, b))) ? a : a.concat(b), []);
35 | }
36 |
37 | /***
38 | * Checks deep equality for objects
39 | * @param objA
40 | * @param objB
41 | * @returns {boolean}
42 | */
43 | static deepEqual(objA, objB) {
44 | const keys = Object.keys, typeA = typeof objA, typeB = typeof objB;
45 | return objA && objB && typeA === 'object' && typeA === typeB ? (
46 | keys(objA).length === keys(objB).length &&
47 | keys(objA).every(key => this.deepEqual(objA[key], objB[key]))
48 | ) : (objA === objB);
49 | }
50 |
51 | /***
52 | * Flattens an array into a single dimension
53 | * @param array
54 | * @returns {*}
55 | */
56 | static flatten(array){
57 | return array.reduce(
58 | (a, b) => a.concat(Array.isArray(b) ? this.flatten(b) : b), []
59 | );
60 | }
61 |
62 | /***
63 | * Flattens an objects keys into a single dimension
64 | * @param object
65 | * @returns {*}
66 | */
67 | static objectToFlatKeys(object){
68 | return this.flatten(Object.keys(object).map(key => {
69 | if(object[key] !== null && typeof object[key] === 'object') return this.objectToFlatKeys(object[key])
70 | else return key;
71 | }))
72 | }
73 |
74 | /***
75 | * Gets a field from an object by string dot notation, such as `location.country.code`
76 | * @param object
77 | * @param dotNotation
78 | * @returns {*}
79 | */
80 | static getFieldFromObjectByDotNotation(object, dotNotation){
81 | let props = dotNotation.split(".");
82 | return props.reduce((obj,key)=> obj[key], object)
83 | }
84 |
85 | }
--------------------------------------------------------------------------------
/src/util/StringHelpers.js:
--------------------------------------------------------------------------------
1 | export default class StringHelpers {
2 |
3 | static camelToTitle(camel){
4 | return camel
5 | .replace(/([A-Z])/g, (match) => ` ${match}`)
6 | .replace(/^./, (match) => match.toUpperCase());
7 | }
8 |
9 | }
--------------------------------------------------------------------------------
/src/util/TimingHelpers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A set of helpers for Timing operations
3 | */
4 | export default class TimingHelpers {
5 |
6 | /***
7 | * Returns the milliseconds that has passed since the given time (unix epoch)
8 | * @param time - past time in unix epoch format
9 | * @returns {number} - difference from now (unix epoch)
10 | */
11 | static since(time) {
12 | return Date.now() - time;
13 | }
14 |
15 | /***
16 | * Returns the milliseconds of the given minutes
17 | * @param min - minutes, integer format
18 | * @returns {number} - minutes in milliseconds
19 | */
20 | static minutes(min) {
21 | return min * 60000;
22 | }
23 |
24 | /***
25 | * Returns the minutes based on the given milliseconds
26 | * @param millis - milliseconds
27 | * @returns {number} - minutes, integer
28 | */
29 | static minutesFromMillis(millis) {
30 | return millis / 60000;
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/src/views/AutoLockView.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
70 |
71 |
--------------------------------------------------------------------------------
/src/views/BackupView.vue:
--------------------------------------------------------------------------------
1 |
2 |
23 |
24 |
25 |
84 |
85 |
--------------------------------------------------------------------------------
/src/views/ChangePasswordView.vue:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
24 |
71 |
72 |
--------------------------------------------------------------------------------
/src/views/DestroyView.vue:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
71 |
72 |
--------------------------------------------------------------------------------
/src/views/EntryView.vue:
--------------------------------------------------------------------------------
1 |
2 |
27 |
28 |
29 |
85 |
86 |
99 |
--------------------------------------------------------------------------------
/src/views/KeyPairsView.vue:
--------------------------------------------------------------------------------
1 |
2 |
39 |
40 |
41 |
105 |
106 |
--------------------------------------------------------------------------------
/src/views/LanguageView.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
62 |
63 |
--------------------------------------------------------------------------------
/src/views/LoadFromBackup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
76 |
77 |
101 |
--------------------------------------------------------------------------------
/src/views/NetworkView.vue:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
26 |
100 |
101 |
--------------------------------------------------------------------------------
/src/views/NetworksView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
41 |
42 |
43 |
44 |
97 |
98 |
--------------------------------------------------------------------------------
/src/views/PermissionsView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{perms.length}}
10 | {{domain}}
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
62 |
63 |
--------------------------------------------------------------------------------
/src/views/SettingsView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{link.name}}
7 |
8 |
9 |
10 |
11 |
12 |
34 |
35 |
--------------------------------------------------------------------------------
/src/views/ShowMnemonicView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{locale(langKeys.MNEMONIC_Header)}}
7 |
8 |
9 | {{locale(langKeys.MNEMONIC_Description)}}
10 |
11 |
12 | {{locale(langKeys.MNEMONIC_Note)}}
13 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
57 |
58 |
--------------------------------------------------------------------------------
/src/views/ViewBase.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
48 |
49 |
--------------------------------------------------------------------------------
/src/vue/VueInitializer.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | import VueRouter from 'vue-router'
4 | import VueMoment from 'vue-moment';
5 | import {Routing} from './Routing';
6 | import {store} from '../store/store'
7 | import * as Actions from '../store/constants'
8 | import {localized} from '../localization/locales'
9 | import * as LANG_KEYS from '../localization/keys'
10 |
11 | /***
12 | * Sets up an instance of Vue.
13 | * This is shared between the popup.js and prompt.js scripts.
14 | */
15 | export default class VueInitializer {
16 |
17 | constructor(routes,
18 | components,
19 | middleware = () => {},
20 | routerCallback = () => {}){
21 | this.setupVuePlugins();
22 | this.registerComponents(components);
23 | const router = this.setupRouting(routes, middleware);
24 |
25 | store.dispatch(Actions.LOAD_SCATTER).then(() => {
26 |
27 | Vue.mixin({
28 | data(){ return {
29 | langKeys:LANG_KEYS
30 | }},
31 | methods: {
32 | locale:(key) => localized(key, store.getters.language),
33 | }
34 | })
35 |
36 | this.setupVue(router);
37 |
38 | routerCallback(router, store);
39 | });
40 |
41 | return router;
42 | }
43 |
44 | setupVuePlugins(){
45 | Vue.use(VueRouter);
46 | Vue.use(VueMoment);
47 | }
48 |
49 | registerComponents(components){
50 | components.map(component => {
51 | Vue.component(component.tag, component.vue);
52 | });
53 | }
54 |
55 | setupRouting(routes, middleware){
56 | const router = new VueRouter({routes});
57 | router.beforeEach((to, from, next) => middleware(to, next, store));
58 | return router;
59 | }
60 |
61 | setupVue(router){
62 | const app = new Vue({router, store});
63 | app.$mount('#scatter');
64 |
65 | // This removes the browser console's ability to
66 | // gain access to vuex store. ( for instance `scatter.__vue__.$store.state` )
67 | document.getElementById('scatter').removeAttribute('id')
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/tests/helpers/chrome.js:
--------------------------------------------------------------------------------
1 | const chrome = {
2 | storage: {
3 | local: {
4 | stored:{},
5 | set:function(keyobj, cb){
6 | Object.keys(keyobj).map(key => chrome.storage.local.stored[key] = keyobj[key])
7 | cb(true);
8 | },
9 | get:function(key, cb){
10 | cb(
11 | chrome.storage.local.stored.hasOwnProperty(key) ?
12 | {[key]:chrome.storage.local.stored[key]}
13 | : {}
14 | )
15 | },
16 | remove:function(key, cb){
17 | delete chrome.storage.local.stored[key];
18 | cb(true);
19 | }
20 | }
21 | },
22 | runtime:{
23 | id:'TEST',
24 | sendMessage:function(msg, cb){
25 | this.onMessage.trigger(msg, cb)
26 | },
27 | onMessage:{
28 | messageQueue:[],
29 | trigger:function(data, cb) { this.messageQueue.push({data, cb}) },
30 | addListener:function(cb){
31 | setInterval(() => {
32 | if(this.messageQueue.length){
33 | let message = this.messageQueue.pop();
34 | cb(message.data, {id:'TEST'}, message.cb)
35 | }
36 | }, 100)
37 | }
38 | }
39 | }
40 | };
41 |
42 | export default chrome;
--------------------------------------------------------------------------------
/tests/localization/locales.spec.js:
--------------------------------------------------------------------------------
1 | import {locales} from '../../src/localization/locales'
2 | import chrome from '../helpers/chrome';
3 |
4 | import Eos from 'eosjs';
5 |
6 | import { expect, assert } from 'chai';
7 | import 'mocha';
8 |
9 |
10 | describe('Signature Request', async testDone => {
11 |
12 | global.chrome = chrome;
13 |
14 | it('should work', done => {
15 | console.log('locales', locales());
16 | done();
17 | });
18 |
19 |
20 |
21 | });
--------------------------------------------------------------------------------
/tests/migrations/5.0.4.spec.js:
--------------------------------------------------------------------------------
1 | import Network from '../../src/models/Network'
2 |
3 | const {m5_0_4} = require('../../src/migrations/versions/5.0.4');
4 |
5 | import { expect, assert } from 'chai';
6 | import 'mocha';
7 |
8 | describe('Identity', () => {
9 |
10 | it('should upgrade a scatter instances endorsed networks', done => {
11 | new Promise(async () => {
12 | const fakeScatter = {
13 | settings:{
14 | networks:[
15 | Network.fromJson({host:'nodes.get-scatter.com', port:80}),
16 | Network.fromJson({host:'tester.com', port:80}),
17 | Network.fromJson({host:'tester2.com', port:443})
18 | ]
19 | }
20 | };
21 |
22 | await m5_0_4(fakeScatter);
23 |
24 | assert(fakeScatter.settings.networks[0].name === 'EOS Mainnet', 'EOS Endorsed network was not named');
25 | assert(fakeScatter.settings.networks[1].protocol === 'http', 'Port 80 got the wrong protocol');
26 | assert(fakeScatter.settings.networks[2].protocol === 'https', 'Port 443 got the wrong protocol');
27 |
28 | done();
29 | });
30 | })
31 | });
--------------------------------------------------------------------------------
/tests/migrations/6.0.4.spec.js:
--------------------------------------------------------------------------------
1 | import Network from '../../src/models/Network'
2 | import PluginRepository from '../../src/plugins/PluginRepository';
3 | import {Blockchains} from '../../src/models/Blockchains'
4 |
5 | const {m6_0_4} = require('../../src/migrations/versions/6.0.4');
6 |
7 | import { expect, assert } from 'chai';
8 | import 'mocha';
9 |
10 | describe('Identity', () => {
11 |
12 | it('should upgrade a scatter instances endorsed networks', done => {
13 | new Promise(async () => {
14 | const eosEnd = await PluginRepository.plugin(Blockchains.EOS).getEndorsedNetwork();
15 | const eodEndWrongPort = eosEnd;
16 | eosEnd.port = 80;
17 | const fakeScatter = {
18 | keychain:{
19 | identities:[
20 | {
21 | accounts:{
22 | 'unlinkednetwork':{shouldBe:'deleted'},
23 | [eosEnd.unique()]:{shouldBe:'left'}
24 | }
25 | }
26 | ]
27 | },
28 | settings:{
29 | networks:[eodEndWrongPort]
30 | }
31 | };
32 |
33 | await m6_0_4(fakeScatter);
34 |
35 | assert(fakeScatter.settings.networks[0].port === 443, 'EOS Endorsed network had a bad port');
36 | assert(Object.keys(fakeScatter.keychain.identities[0].accounts).length === 1, 'Too many accounts');
37 | assert(JSON.stringify(fakeScatter.keychain.identities[0].accounts[eosEnd.unique()]) === JSON.stringify({shouldBe:'left'}), 'Did not delete the right accounts');
38 |
39 | done();
40 | });
41 | })
42 | });
--------------------------------------------------------------------------------
/tests/pk2pk.spec.js:
--------------------------------------------------------------------------------
1 | import {assert} from 'chai';
2 | import 'mocha';
3 |
4 | import PluginRepository from '../src/plugins/PluginRepository';
5 | import {Blockchains} from '../src/models/Blockchains'
6 |
7 | import ecc from 'eosjs-ecc';
8 | import secp256k1 from 'secp256k1';
9 | import utils from 'ethereumjs-util';
10 |
11 | describe('Ethereum key to EOS key', () => {
12 |
13 | const eth = PluginRepository.plugin(Blockchains.ETH);
14 | const eos = PluginRepository.plugin(Blockchains.EOS);
15 |
16 | let ethPrivateKey = '';
17 | let ethPublicKey = '';
18 | let eosPublicKey = '';
19 |
20 | it('should generate base keys', done => {
21 | new Promise(async(resolve, reject) => {
22 | ethPrivateKey = await eth.randomPrivateKey();
23 | ethPublicKey = '0x'+utils.privateToPublic(utils.toBuffer(utils.addHexPrefix(ethPrivateKey))).toString('hex')
24 | done();
25 | });
26 | });
27 |
28 | it('should convert publicKeys', () => {
29 | let buffer = Buffer.from(ethPublicKey.slice(2), 'hex'),
30 | converted = secp256k1.publicKeyConvert(Buffer.concat([ Buffer.from([4]), buffer ]), true);
31 | eosPublicKey = ecc.PublicKey.fromBuffer(converted).toString();
32 | assert(eos.validPublicKey(eosPublicKey), "Eos public key was not valid")
33 | });
34 |
35 | it('should convert keys', () => {
36 | let ethBuffer = Buffer.from(ethPrivateKey, 'hex');
37 | const p = ecc.PrivateKey.fromHex(ethBuffer);
38 | const pk = ecc.privateToPublic(p);
39 | assert(eosPublicKey === pk, "Converted public and converted private -> public key does not match");
40 | })
41 |
42 | });
--------------------------------------------------------------------------------
/tests/services/StorageService.spec.js:
--------------------------------------------------------------------------------
1 | import StorageService from '../../src/services/StorageService';
2 | import chrome from '../helpers/chrome'
3 | import { expect, assert } from 'chai';
4 | import 'mocha';
5 |
6 |
7 |
8 | describe('StorageService', () => {
9 |
10 | global.chrome = chrome;
11 |
12 | let scatter = null;
13 |
14 | it('should be able to get an instance of scatter from the storage even if one does not exist', (done) => {
15 | StorageService.get().then(stored => {
16 | scatter = stored;
17 | done();
18 | })
19 | });
20 |
21 | it('should be able to save an instance of scatter to the storage', (done) => {
22 | scatter.meta.version = '1.2';
23 | StorageService.save(scatter).then(saved => {
24 | StorageService.get().then(stored => {
25 | assert(stored.meta.version === scatter.meta.version, "Saved scatter does not match");
26 | done();
27 | })
28 | })
29 | });
30 |
31 | it('should be able to remove the instance of scatter from the storage', (done) => {
32 | StorageService.remove().then(saved => {
33 | StorageService.get().then(stored => {
34 | assert(stored.meta.version !== scatter.meta.version, "Saved scatter does not match");
35 | done();
36 | })
37 | })
38 | });
39 |
40 | });
--------------------------------------------------------------------------------
/tests/setup.js:
--------------------------------------------------------------------------------
1 |
2 | require('jsdom-global')()
3 | global.chrome = {
4 | storage: {
5 | local: {
6 | stored:{},
7 | set:function(keyobj, cb){
8 | Object.keys(keyobj).map(key => chrome.storage.local.stored[key] = keyobj[key])
9 | cb(true);
10 | },
11 | get:function(key, cb){
12 | cb(
13 | chrome.storage.local.stored.hasOwnProperty(key) ?
14 | {[key]:chrome.storage.local.stored[key]}
15 | : {}
16 | )
17 | },
18 | remove:function(key, cb){
19 | delete chrome.storage.local.stored[key];
20 | cb(true);
21 | }
22 | }
23 | },
24 | runtime:{
25 | id:'TEST',
26 | sendMessage:function(msg, cb){
27 | this.onMessage.trigger(msg, cb)
28 | },
29 | onMessage:{
30 | messageQueue:[],
31 | trigger:function(data, cb) { this.messageQueue.push({data, cb}) },
32 | addListener:function(cb){
33 | setInterval(() => {
34 | if(this.messageQueue.length){
35 | let message = this.messageQueue.pop();
36 | cb(message.data, {id:'TEST'}, message.cb)
37 | }
38 | }, 100)
39 | }
40 | }
41 | }
42 | };
--------------------------------------------------------------------------------
/tests/signers/eos.spec.js:
--------------------------------------------------------------------------------
1 | import Scatter from '../../src/models/Scatter'
2 | import Scatterdapp from '../../src/scatterdapp'
3 | import KeyPair from '../../src/models/KeyPair'
4 | import Account from '../../src/models/Account'
5 | import Identity from '../../src/models/Identity'
6 | import {PersonalInformation, LocationInformation} from '../../src/models/Identity'
7 | import Network from '../../src/models/Network'
8 | import chrome from '../helpers/chrome';
9 | import PluginRepository from '../../src/plugins/PluginRepository'
10 | import {EncryptedStream} from 'extension-streams';
11 | import {Blockchains} from '../../src/models/Blockchains'
12 |
13 | import Eos from 'eosjs';
14 |
15 | import { expect, assert } from 'chai';
16 | import 'mocha';
17 |
18 |
19 | describe('Signature Request', async testDone => {
20 |
21 | global.chrome = chrome;
22 |
23 | const network = Network.fromJson({
24 | host:'x.x.x.x',
25 | port:'0000'
26 | });
27 |
28 | // Using a pre-generated keypair to evade entropy gathering
29 | const keypair = KeyPair.fromJson({
30 | publicKey:'EOS68fyx6VBXvk4TtEC8GL5GP5qJcod9f7ZcZWBTVSbAYhAwkWkQu',
31 | privateKey:'5KLkgBvMi1Ed9x2xWkiNdZNYZR1DSfJxVqK9fhRtDuKucn1bcUY'
32 | });
33 |
34 | const account = Account.fromJson({
35 | publicKey:keypair.publicKey,
36 | name:'test123',
37 | authority:'active'
38 | });
39 |
40 | const identity = new Identity();
41 | identity.name = 'e2eTest';
42 | identity.personal = PersonalInformation.fromJson({
43 | firstname:'hello',
44 | lastname:'world',
45 | email:'asdf@asdf.com'
46 | });
47 | identity.locations = [LocationInformation.fromJson({
48 | name:'Home',
49 | isDefault:true,
50 | phone:'47912559',
51 | address:'1561 Dylan Dr',
52 | city:'Virginia Beach',
53 | state:'VA',
54 | country:{code:'US', name:'United States'},
55 | zipcode:'23464'
56 | })];
57 | identity.setAccount(network, account);
58 |
59 |
60 | const scatter = new Scatter();
61 | scatter.keychain.keypairs.push(keypair);
62 | scatter.keychain.identities.push(identity);
63 |
64 | // TODO: Update to dawn 3 once eosjs is ready
65 | let unsignedTransaction = {
66 | scope: [account.name, 'inita'],
67 | messages: [
68 | {
69 | code: 'eos',
70 | type: 'transfer',
71 | authorization: [{
72 | account: account.name,
73 | permission: 'active'
74 | }],
75 | data: {
76 | from: account.name,
77 | to: 'inita',
78 | amount: 1,
79 | memo: 'Test'
80 | }
81 | }
82 | ]
83 | };
84 |
85 | const eos = Eos({
86 | keyProvider: [keypair.privateKey],
87 | mockTransactions: () => 'pass',
88 | broadcast: false,
89 | sign: true
90 | });
91 |
92 | // it('should initialize deferred mock vars', done => {
93 | // identity.initialize().then(done());
94 | // });
95 |
96 | it('should be able to set up a transaction to be signed', done => {
97 |
98 | const signProvider = PluginRepository.plugin(Blockchains.EOS).signatureProvider();
99 | eos.transfer('inita', 'initb', '10.000 EOS', '', {signProvider}).then(done => {
100 |
101 | });
102 |
103 | done();
104 | });
105 |
106 |
107 |
108 | });
--------------------------------------------------------------------------------
/tests/util/ContractHelpers.spec.js:
--------------------------------------------------------------------------------
1 | import ContractHelpers from '../../src/util/ContractHelpers'
2 | import { expect, assert } from 'chai';
3 | import 'mocha';
4 |
5 | describe('ContractHelpers', () => {
6 |
7 | const domain = 'test.com';
8 | const network = { host:'test.com', port:8080 };
9 |
10 | const message = {
11 | authorization:[{ account:'test', permission:'active' }],
12 | code:'eos',
13 | type:'transfer',
14 | data:{
15 | from:'test',
16 | to:'inita',
17 | amount:'10',
18 | memo:''
19 | }
20 | };
21 |
22 | const differentMessages = [
23 | {
24 | authorization:[{ account:'inita', permission:'active' }],
25 | code:'eos',
26 | type:'transfer',
27 | data:{
28 | from:'inita',
29 | to:'test',
30 | amount:'10',
31 | memo:''
32 | }
33 | },
34 | {
35 | authorization:[{ account:'test', permission:'active' }],
36 | code:'eos',
37 | type:'transfer',
38 | data:{
39 | from:'test',
40 | to:'inita',
41 | amount:'11',
42 | memo:''
43 | }
44 | },
45 | {
46 | authorization:[{ account:'test', permission:'active' }],
47 | code:'eos',
48 | type:'something_else',
49 | data:{
50 | from:'test',
51 | to:'inita',
52 | amount:'10',
53 | memo:''
54 | }
55 | },
56 | {
57 | authorization:[{ account:'test', permission:'active' }],
58 | code:'eos2',
59 | type:'transfer',
60 | data:{
61 | from:'test',
62 | to:'inita',
63 | amount:'10',
64 | memo:''
65 | }
66 | }
67 | ];
68 |
69 | let checksum = '';
70 |
71 | it('should be able to create a checksum', () => {
72 | checksum = ContractHelpers.messageChecksum(message, 'test', domain, network);
73 | });
74 |
75 | it('should know that a contract itself or the message conditions have changed and should fail the checksum validation', () => {
76 | differentMessages.map(msg => {
77 | assert(!ContractHelpers.validChecksum(checksum, msg, 'test', domain, network), "checksum came back valid but should not have");
78 | })
79 | });
80 |
81 | it('should know that an exact same message is valid by it\'s checksum', () => {
82 | assert(ContractHelpers.validChecksum(checksum, message, 'test', domain, network));
83 | });
84 |
85 | });
--------------------------------------------------------------------------------
/tests/util/IdGenerator.spec.js:
--------------------------------------------------------------------------------
1 | import IdGenerator from '../../src/util/IdGenerator'
2 | import { expect, assert } from 'chai';
3 | import 'mocha';
4 |
5 | describe('IdGenerator', () => {
6 |
7 | it('should be able to generate a random textual id', () => {
8 | const random32 = IdGenerator.text(32);
9 | const random32_2 = IdGenerator.text(32);
10 | const random64 = IdGenerator.text(64);
11 | const random128 = IdGenerator.text(128);
12 | assert(random32.length === 32);
13 | assert(random32 !== random32_2);
14 | assert(random64.length === 64);
15 | assert(random128.length === 128);
16 | });
17 |
18 | it('should be able to generate a random numeric id', () => {
19 | const random32 = IdGenerator.numeric(32);
20 | const random32_2 = IdGenerator.numeric(32);
21 | const random64 = IdGenerator.numeric(64);
22 | const random128 = IdGenerator.numeric(128);
23 | assert(random32.length === 32);
24 | assert(random32 !== random32_2);
25 | assert(random64.length === 64);
26 | assert(random128.length === 128);
27 | assert(!isNaN(random32));
28 | });
29 |
30 | });
--------------------------------------------------------------------------------
/tests/util/ObjectHelpers.spec.js:
--------------------------------------------------------------------------------
1 | import ObjectHelpers from '../../src/util/ObjectHelpers'
2 | import {LocationFields, LocationInformation} from '../../src/models/Identity'
3 | import { expect, assert } from 'chai';
4 | import 'mocha';
5 |
6 | describe('ObjectHelpers', () => {
7 |
8 | const location = LocationInformation.fromJson({
9 | name:'test',
10 | country:{code:'US', name:'United States'}
11 | });
12 |
13 | it('should be able to get a property from a field using dot notation', () => {
14 | assert(ObjectHelpers.getFieldFromObjectByDotNotation(location, 'country.code') === 'US',
15 | "Could not get `country.code` property from the object.");
16 |
17 | assert(ObjectHelpers.getFieldFromObjectByDotNotation(location, 'name') === 'test',
18 | "Could not get `name` property from the object.");
19 | });
20 |
21 | it('should return undefined from non-existent properties', () => {
22 | assert(ObjectHelpers.getFieldFromObjectByDotNotation(location, 'noprop') === undefined,
23 | "Trying to get non existant prop was not undefined");
24 | });
25 |
26 | });
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack')
3 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
5 | const IgnoreEmitPlugin = require('ignore-emit-webpack-plugin');
6 | const ZipPlugin = require('zip-webpack-plugin');
7 | const CopyWebpackPlugin = require('copy-webpack-plugin');
8 | const nodeExternals = require('webpack-node-externals')
9 | const Dotenv = require('dotenv-webpack');
10 |
11 | console.log(process.env.SCATTER_ENV);
12 |
13 | const production = process.env.SCATTER_ENV === 'production';
14 | const vueAlias = `vue/dist/vue${production ? '.min' : ''}.js`;
15 |
16 | const devPlugins = [
17 | new webpack.DefinePlugin({
18 | 'process.env': {
19 | NODE_ENV: '"production"'
20 | }
21 | })
22 | ];
23 | const prodPlugins = devPlugins.concat([
24 | new ZipPlugin({ path: '../', filename: 'scatter.zip' }),
25 | new UglifyJsPlugin(),
26 | ])
27 |
28 | const productionPlugins = !production ? devPlugins : prodPlugins;
29 |
30 | const runningTests = process.env.SCATTER_ENV === 'testing';
31 | const externals = runningTests ? [nodeExternals()] : [];
32 |
33 | function replaceSuffixes(file){
34 | return file
35 | .replace('scss', 'css')
36 | }
37 |
38 | const filesToPack = [
39 | 'background.js',
40 | 'popup.js',
41 | 'content.js',
42 | 'inject.js',
43 | 'prompt.js',
44 | 'scatterdapp.js',
45 | 'styles.scss',
46 | ];
47 | const entry = filesToPack.reduce((o, file) => Object.assign(o, {[replaceSuffixes(file)]: `./src/${file}`}), {});
48 |
49 | module.exports = {
50 | entry,
51 | output: {
52 | path: path.resolve(__dirname, './build'),
53 | filename: '[name]'
54 | },
55 | resolve: {
56 | alias: {
57 | vue: vueAlias,
58 | 'extension-streams': 'extension-streams/dist/index.js',
59 | 'aes-oop': 'aes-oop/dist/AES.js',
60 | },
61 | modules: [ path.join(__dirname, "node_modules") ]
62 | },
63 | module: {
64 | loaders: [
65 | { test: /\.js$/, loader: 'babel-loader', query: { presets: ['es2015', 'stage-3'] }, exclude: /node_modules/ }
66 | ],
67 | rules:[
68 | { test: /\.(sass|scss)$/, loader: ExtractTextPlugin.extract(['css-loader', 'sass-loader']) },
69 | { test: /\.(html|png)$/, loader: 'file-loader', options: { name: '[name].[ext]' } },
70 | { test: /\.vue$/, loader: 'vue-loader', options: {
71 | loaders: {
72 | js: 'babel-loader',
73 | scss: 'vue-style-loader!css-loader!sass-loader'
74 | }
75 | } }
76 | ]
77 | },
78 | plugins: [
79 | new ExtractTextPlugin({ filename: '[name]', allChunks: true }),
80 | new IgnoreEmitPlugin(/\.omit$/),
81 | new CopyWebpackPlugin([`./src/copied`]),
82 | new Dotenv({
83 | path: './.env',
84 | safe: true
85 | })
86 | ].concat(productionPlugins),
87 | stats: { colors: true },
88 | devtool: 'source-map', //inline-
89 | externals
90 | }
--------------------------------------------------------------------------------