Initial commit aka code from heaven
This commit is contained in:
commit
0b6bcd2bb9
1
.babelrc.js
Normal file
1
.babelrc.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./babel.config.js');
|
||||||
6
.codeclimate.yml
Normal file
6
.codeclimate.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
exclude_patterns:
|
||||||
|
- "**/*.spec.js"
|
||||||
|
- "**/*.spec.ts"
|
||||||
|
- "**/*.test.js"
|
||||||
|
- "**/*.test.ts"
|
||||||
|
- "docs/**/*.js"
|
||||||
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
root = true
|
||||||
|
[*]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=2
|
||||||
|
tab_width=2
|
||||||
|
end_of_line=lf
|
||||||
|
charset=utf-8
|
||||||
|
trim_trailing_whitespace=true
|
||||||
|
max_line_length=120
|
||||||
|
insert_final_newline=true
|
||||||
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
build/
|
||||||
|
coverage/
|
||||||
|
node_modules/
|
||||||
|
tmp/
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.npmrc
|
||||||
|
lerna-debug.log*
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
package-lock.json
|
||||||
|
_book
|
||||||
|
docs/html
|
||||||
1
.npmignore
Normal file
1
.npmignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
14
.travis.yml
Normal file
14
.travis.yml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "10"
|
||||||
|
before_install:
|
||||||
|
- curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||||
|
- export PATH=$HOME/.yarn/bin::$(yarn global bin):$PATH
|
||||||
|
cache:
|
||||||
|
yarn: true
|
||||||
|
directories:
|
||||||
|
- node_modules
|
||||||
|
install:
|
||||||
|
- yarn
|
||||||
|
script:
|
||||||
|
- yarn polkadot-dev-build-travis
|
||||||
0
CHANGELOG.md
Normal file
0
CHANGELOG.md
Normal file
45
CONTRIBUTING.md
Normal file
45
CONTRIBUTING.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## What?
|
||||||
|
|
||||||
|
Individuals making significant and valuable contributions are given commit-access to a project to contribute as they see fit.
|
||||||
|
A project is more like an open wiki than a standard guarded open source project.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
There are a few basic ground-rules for contributors (including the maintainer(s) of the project):
|
||||||
|
|
||||||
|
1. **No `--force` pushes** or modifying the Git history in any way. If you need to rebase, ensure you do it in your own repo.
|
||||||
|
2. **Non-master branches**, prefixed with a short name moniker (e.g. `<initials>-<feature>`) must be used for ongoing work.
|
||||||
|
3. **All modifications** must be made in a **pull-request** to solicit feedback from other contributors.
|
||||||
|
4. A pull-request *must not be merged until CI* has finished successfully.
|
||||||
|
|
||||||
|
#### Merging pull requests once CI is successful:
|
||||||
|
- A pull request with no large change to logic that is an urgent fix may be merged after a non-author contributor has reviewed it well.
|
||||||
|
- No PR should be merged until all reviews' comments are addressed.
|
||||||
|
|
||||||
|
#### Reviewing pull requests:
|
||||||
|
When reviewing a pull request, the end-goal is to suggest useful changes to the author. Reviews should finish with approval unless there are issues that would result in:
|
||||||
|
|
||||||
|
- Buggy behaviour.
|
||||||
|
- Undue maintenance burden.
|
||||||
|
- Breaking with house coding style.
|
||||||
|
- Pessimisation (i.e. reduction of speed as measured in the projects benchmarks).
|
||||||
|
- Feature reduction (i.e. it removes some aspect of functionality that a significant minority of users rely on).
|
||||||
|
- Uselessness (i.e. it does not strictly add a feature or fix a known issue).
|
||||||
|
|
||||||
|
#### Reviews may not be used as an effective veto for a PR because:
|
||||||
|
- There exists a somewhat cleaner/better/faster way of accomplishing the same feature/fix.
|
||||||
|
- It does not fit well with some other contributors' longer-term vision for the project.
|
||||||
|
|
||||||
|
## Releases
|
||||||
|
|
||||||
|
Declaring formal releases remains the prerogative of the project maintainer(s).
|
||||||
|
|
||||||
|
## Changes to this arrangement
|
||||||
|
|
||||||
|
This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change.
|
||||||
|
|
||||||
|
## Heritage
|
||||||
|
|
||||||
|
These contributing guidelines are modified from the "OPEN Open Source Project" guidelines for the Level project: [https://github.com/Level/community/blob/master/CONTRIBUTING.md](https://github.com/Level/community/blob/master/CONTRIBUTING.md)
|
||||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
55
README.md
Normal file
55
README.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# @polkadot/extension
|
||||||
|
|
||||||
|
A very simple scaffolding browser extension that injects a [@polkadot/api](https://github.com/polkadot-js/api) Signer into a page, along with any associated accounts, allowing for use by the application. This is an extensible POC implementation of a Polkadot/Substrate browser signer.
|
||||||
|
|
||||||
|
As it stands, it does one thing: it _only_ manages accounts and allows the signing of transactions with those accounts. (It does not inject providers for use by dapps, not does it perform the function of a wallet where it submits txs to the network).
|
||||||
|
|
||||||
|
# Running
|
||||||
|
|
||||||
|
Currently is not packaged since it is under heavy development. To use -
|
||||||
|
|
||||||
|
1. Build via `yarn build` or `yarn watch`
|
||||||
|
2. Add the extension under `chrome://extensions/` (ensure you have the Development flag set) and "Load unpacked"
|
||||||
|
3. When visiting `http://localhost:3000` or `https://polkadot.js.org/apps/` it will inject (the menifest currently only lists these 2 endpoints)
|
||||||
|
|
||||||
|
Once added, you can create an account (via a generated seed) or import via an existing seed.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
It injects `injectedWeb3` into the global `window` object, exposing the following:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// a version that identifies the actual injection version (future-use)
|
||||||
|
type Version = 0;
|
||||||
|
|
||||||
|
// an interface describing an account
|
||||||
|
interface Account {
|
||||||
|
readonly address: string; // ss-58 encoded address
|
||||||
|
readonly name?: string; // optional name for display
|
||||||
|
}
|
||||||
|
|
||||||
|
// exposes accounts
|
||||||
|
interface Accounts {
|
||||||
|
readonly all: Array<Account>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a signer that communicates with the extension via sendMessage
|
||||||
|
interface Signer extends SignerInterface {
|
||||||
|
// no specific signer extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Injected {
|
||||||
|
readonly accounts: Accounts;
|
||||||
|
readonly signer: Signer;
|
||||||
|
readonly version: Version;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The app can use all or any of these, depending on needs. To instantiate the `@polkadot/api` with the provider (app does not have it's own) and the signer (allowing the extension to sign messages) can be done via -
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { ApiPromise } from '@polkadot/api';
|
||||||
|
|
||||||
|
const { signer } = window.injectedWeb3;
|
||||||
|
const api = await Api.create({ signer });
|
||||||
|
```
|
||||||
3
babel.config.js
Normal file
3
babel.config.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: '@polkadot/dev/config/babel'
|
||||||
|
};
|
||||||
13
jest.config.js
Normal file
13
jest.config.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const config = require('@polkadot/dev/config/jest');
|
||||||
|
|
||||||
|
module.exports = Object.assign({}, config, {
|
||||||
|
moduleNameMapper: {
|
||||||
|
'@polkadot/extension-(ui)(.*)$': '<rootDir>/packages/extension-$1/src/$2',
|
||||||
|
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 'empty/object',
|
||||||
|
'\\.(css|less)$': 'empty/object'
|
||||||
|
},
|
||||||
|
modulePathIgnorePatterns: [
|
||||||
|
'<rootDir>/packages/extension/build',
|
||||||
|
'<rootDir>/packages/extension-ui/build'
|
||||||
|
]
|
||||||
|
});
|
||||||
13
lerna.json
Normal file
13
lerna.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"npmClient": "yarn",
|
||||||
|
"useWorkspaces": true,
|
||||||
|
"command": {
|
||||||
|
"publish": {
|
||||||
|
"allowBranch": "master"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packages": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"version": "0.1.0"
|
||||||
|
}
|
||||||
34
package.json
Normal file
34
package.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "@polkadot/extension",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "A sample signer extension for the @polkadot/api",
|
||||||
|
"repository": "https://github.com/polkadot-js/extension",
|
||||||
|
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||||
|
"license": "Apache-2",
|
||||||
|
"private": true,
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0",
|
||||||
|
"yarn": "^1.10.1"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
|
"typescript": "^3.4.5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "NODE_ENV=production polkadot-dev-build-ts",
|
||||||
|
"check": "yarn lint",
|
||||||
|
"lint": "tslint --project . && tsc --noEmit --pretty",
|
||||||
|
"clean": "polkadot-dev-clean-build",
|
||||||
|
"postinstall": "polkadot-dev-yarn-only",
|
||||||
|
"test": "jest --coverage",
|
||||||
|
"watch": "cd packages/extension && webpack --config webpack.config.js --watch"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.4.4",
|
||||||
|
"@polkadot/dev-react": "^0.30.0-beta.4",
|
||||||
|
"@polkadot/ts": "^0.1.56"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
packages/extension-ui/package.json
Normal file
14
packages/extension-ui/package.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "@polkadot/extension-ui",
|
||||||
|
"description": "A sample signer extension for the @polkadot/api",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||||
|
"license": "Apache-2",
|
||||||
|
"dependencies": {
|
||||||
|
"@polkadot/extension-inject": "^0.1.0",
|
||||||
|
"@polkadot/ui-identicon": "^0.39.0-beta.5",
|
||||||
|
"@types/react-router-dom": "^4.3.2",
|
||||||
|
"react-router": "^5.0.0",
|
||||||
|
"react-router-dom": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
42
packages/extension-ui/src/Popup/Accounts.tsx
Normal file
42
packages/extension-ui/src/Popup/Accounts.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { KeyringJson } from '@polkadot/ui-keyring/types';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Address, Button, Header, Tip } from '../components';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
accounts: Array<KeyringJson>
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Accounts ({ accounts }: Props) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Header label='accounts' />
|
||||||
|
{
|
||||||
|
(accounts.length === 0)
|
||||||
|
? <Tip header='add accounts' type='warn'>You currently don't have any accounts. Either create a new account or if you have an existing account you wish to use, import it with the seed phrase</Tip>
|
||||||
|
: accounts.map(({ address, meta: { name } }) => (
|
||||||
|
<Address
|
||||||
|
address={address}
|
||||||
|
key={address}
|
||||||
|
name={name}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
<Button
|
||||||
|
isFull
|
||||||
|
label='I want to create a new account with a new seed'
|
||||||
|
to='/account/create'
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
isFull
|
||||||
|
label='I have a pre-existing seed, import the account'
|
||||||
|
to='/account/import'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
69
packages/extension-ui/src/Popup/Create.tsx
Normal file
69
packages/extension-ui/src/Popup/Create.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { Address, Button, Header, Loading, TextArea } from '../components';
|
||||||
|
import { createAccount, createSeed } from '../messaging';
|
||||||
|
import { Back, Name, Password } from '../partials';
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
|
||||||
|
export default function Create (props: Props) {
|
||||||
|
const [account, setAccount] = useState(null as null | { address: string, seed: string });
|
||||||
|
const [name, setName] = useState(null as string | null);
|
||||||
|
const [password, setPassword] = useState(null as string | null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
createSeed()
|
||||||
|
.then(setAccount)
|
||||||
|
.catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// FIXME Duplicated between here and Import.tsx
|
||||||
|
const _onCreate = (): void => {
|
||||||
|
// this should always be the case
|
||||||
|
if (name && password && account) {
|
||||||
|
createAccount(name, password, account.seed)
|
||||||
|
.then(() => {
|
||||||
|
window.location.hash = '/';
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Header label='create account' />
|
||||||
|
<Back />
|
||||||
|
<Loading>{account && (
|
||||||
|
<>
|
||||||
|
<TextArea
|
||||||
|
isReadOnly
|
||||||
|
label={`generated 12-word mnemonic seed`}
|
||||||
|
value={account.seed}
|
||||||
|
/>
|
||||||
|
<Name
|
||||||
|
isFocussed
|
||||||
|
onChange={setName}
|
||||||
|
/>
|
||||||
|
{name && <Password onChange={setPassword} />}
|
||||||
|
{name && password && (
|
||||||
|
<>
|
||||||
|
<Address
|
||||||
|
address={account.address}
|
||||||
|
name={name}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
isFull
|
||||||
|
label='Add the account with the generated seed'
|
||||||
|
onClick={_onCreate}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}</Loading>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
68
packages/extension-ui/src/Popup/Import.tsx
Normal file
68
packages/extension-ui/src/Popup/Import.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { Address, Button, Header, TextArea } from '../components';
|
||||||
|
import { createAccount, validateSeed } from '../messaging';
|
||||||
|
import { Back, Name, Password } from '../partials';
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
|
||||||
|
export default function Import (props: Props) {
|
||||||
|
const [account, setAccount] = useState(null as null | { address: string, seed: string });
|
||||||
|
const [name, setName] = useState(null as string | null);
|
||||||
|
const [password, setPassword] = useState(null as string | null);
|
||||||
|
|
||||||
|
const _onChangeSeed = (seed: string): void => {
|
||||||
|
validateSeed(seed)
|
||||||
|
.then(setAccount)
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
setAccount(null);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME Duplicated between here and Create.tsx
|
||||||
|
const _onCreate = (): void => {
|
||||||
|
// this should always be the case
|
||||||
|
if (name && password && account) {
|
||||||
|
createAccount(name, password, account.seed)
|
||||||
|
.then(() => {
|
||||||
|
window.location.hash = '/';
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Header label='import account' />
|
||||||
|
<Back />
|
||||||
|
<TextArea
|
||||||
|
isError={!account}
|
||||||
|
isFocussed
|
||||||
|
label={`existing 12 or 24-word mnemonic seed`}
|
||||||
|
onChange={_onChangeSeed}
|
||||||
|
/>
|
||||||
|
{account && <Name onChange={setName} />}
|
||||||
|
{account && name && <Password onChange={setPassword} />}
|
||||||
|
{account && name && password && (
|
||||||
|
<>
|
||||||
|
<Address
|
||||||
|
address={account.address}
|
||||||
|
name={name}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
isFull
|
||||||
|
label='Add the account with the supplied seed'
|
||||||
|
onClick={_onCreate}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
54
packages/extension-ui/src/Popup/Signing/Details.tsx
Normal file
54
packages/extension-ui/src/Popup/Signing/Details.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string,
|
||||||
|
method: string,
|
||||||
|
nonce: string,
|
||||||
|
url: string
|
||||||
|
};
|
||||||
|
|
||||||
|
function Details ({ className, method, nonce, url }: Props) {
|
||||||
|
return (
|
||||||
|
<table className={className}>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className='label'>from</td>
|
||||||
|
<td className='data'>{url}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='label'>nonce</td>
|
||||||
|
<td className='data'>{nonce}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className='label'>method</td>
|
||||||
|
<td className='data'>{method}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styled(Details)`
|
||||||
|
border: 0;
|
||||||
|
display: block;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-left: 4.75rem;
|
||||||
|
|
||||||
|
td.data {
|
||||||
|
max-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: left;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.label {
|
||||||
|
opacity: 0.5;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
`;
|
||||||
54
packages/extension-ui/src/Popup/Signing/Request.tsx
Normal file
54
packages/extension-ui/src/Popup/Signing/Request.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { MessageExtrinsicSign } from '@polkadot/extension/background/types';
|
||||||
|
import { KeyringJson } from '@polkadot/ui-keyring/types';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { ActionBar, Address } from '../../components';
|
||||||
|
import { cancelRequest } from '../../messaging';
|
||||||
|
import Details from './Details';
|
||||||
|
import Unlock from './Unlock';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
accounts: Array<KeyringJson>,
|
||||||
|
className?: string,
|
||||||
|
isFirst: boolean,
|
||||||
|
onAction: () => void,
|
||||||
|
request: MessageExtrinsicSign,
|
||||||
|
signId: number,
|
||||||
|
url: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Request ({ accounts, isFirst, onAction, request: { address, method, nonce }, signId, url }: Props) {
|
||||||
|
const account = accounts.find((account) => address === account.address);
|
||||||
|
|
||||||
|
const _onCancel = (): void => {
|
||||||
|
cancelRequest(signId)
|
||||||
|
.then(onAction)
|
||||||
|
.catch(console.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Address
|
||||||
|
address={address}
|
||||||
|
name={account ? account.meta.name : '<invalid account>'}
|
||||||
|
>
|
||||||
|
<Details
|
||||||
|
method={method}
|
||||||
|
nonce={nonce}
|
||||||
|
url={url}
|
||||||
|
/>
|
||||||
|
<ActionBar>
|
||||||
|
<a onClick={_onCancel}>Cancel</a>
|
||||||
|
</ActionBar>
|
||||||
|
<Unlock
|
||||||
|
isVisible={isFirst}
|
||||||
|
onAction={onAction}
|
||||||
|
signId={signId}
|
||||||
|
/>
|
||||||
|
</Address>
|
||||||
|
);
|
||||||
|
}
|
||||||
45
packages/extension-ui/src/Popup/Signing/Unlock.tsx
Normal file
45
packages/extension-ui/src/Popup/Signing/Unlock.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { Button, Input } from '../../components';
|
||||||
|
import { approveRequest } from '../../messaging';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
signId: number,
|
||||||
|
isVisible: boolean,
|
||||||
|
onAction: () => void
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Unlock ({ isVisible, onAction, signId }: Props) {
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
|
||||||
|
if (!isVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _onSign = (): void => {
|
||||||
|
approveRequest(signId, password)
|
||||||
|
.then(onAction)
|
||||||
|
.catch(console.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Input
|
||||||
|
isError={!password}
|
||||||
|
isFocussed
|
||||||
|
label='password for this account'
|
||||||
|
onChange={setPassword}
|
||||||
|
type='password'
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
isFull
|
||||||
|
label='Sign the extrinsic'
|
||||||
|
onClick={_onSign}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
36
packages/extension-ui/src/Popup/Signing/index.tsx
Normal file
36
packages/extension-ui/src/Popup/Signing/index.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { MessageExtrinsicSign } from '@polkadot/extension/background/types';
|
||||||
|
import { KeyringJson } from '@polkadot/ui-keyring/types';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Header } from '../../components';
|
||||||
|
import Request from './Request';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
accounts: Array<KeyringJson>,
|
||||||
|
requests: Array<[number, MessageExtrinsicSign, string]>,
|
||||||
|
onAction: () => void
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Signing ({ accounts, onAction, requests }: Props) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Header label='requests' />
|
||||||
|
{requests.map(([id, request, url], index) => (
|
||||||
|
<Request
|
||||||
|
accounts={accounts}
|
||||||
|
isFirst={index === 0}
|
||||||
|
key={id}
|
||||||
|
onAction={onAction}
|
||||||
|
request={request}
|
||||||
|
signId={id}
|
||||||
|
url={url}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
packages/extension-ui/src/Popup/index.tsx
Normal file
53
packages/extension-ui/src/Popup/index.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { MessageExtrinsicSign } from '@polkadot/extension/background/types';
|
||||||
|
import { KeyringJson } from '@polkadot/ui-keyring/types';
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Route, Switch } from 'react-router';
|
||||||
|
|
||||||
|
import { Loading } from '../components';
|
||||||
|
import { getAccounts, getRequests } from '../messaging';
|
||||||
|
import Accounts from './Accounts';
|
||||||
|
import Create from './Create';
|
||||||
|
import Import from './Import';
|
||||||
|
import Signing from './Signing';
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
|
||||||
|
export default function Popup (props: Props) {
|
||||||
|
const [accounts, setAccounts] = useState(null as null | Array<KeyringJson>);
|
||||||
|
const [requests, setRequests] = useState(null as null | Array<[number, MessageExtrinsicSign, string]>);
|
||||||
|
|
||||||
|
const _onAction = (): void => {
|
||||||
|
getRequests()
|
||||||
|
.then(setRequests)
|
||||||
|
.catch(console.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect((): void => {
|
||||||
|
Promise
|
||||||
|
.all([getAccounts(), getRequests()])
|
||||||
|
.then(([accounts, requests]) => {
|
||||||
|
setAccounts(accounts);
|
||||||
|
setRequests(requests);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Loading>{accounts && requests && (
|
||||||
|
<Switch>
|
||||||
|
<Route path='/account/create' component={Create} />
|
||||||
|
<Route path='/account/import' component={Import} />
|
||||||
|
<Route exact path='/' component={() => {
|
||||||
|
return requests.length
|
||||||
|
? <Signing accounts={accounts} onAction={_onAction} requests={requests} />
|
||||||
|
: <Accounts accounts={accounts} />;
|
||||||
|
}} />
|
||||||
|
</Switch>
|
||||||
|
)}</Loading>
|
||||||
|
);
|
||||||
|
}
|
||||||
31
packages/extension-ui/src/components/ActionBar.tsx
Normal file
31
packages/extension-ui/src/components/ActionBar.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import defaults from './defaults';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function ActionBar ({ children, className }: Props) {
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styled(ActionBar)`
|
||||||
|
padding: ${defaults.boxPadding};
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
a {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 0.75rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
70
packages/extension-ui/src/components/Address.tsx
Normal file
70
packages/extension-ui/src/components/Address.tsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import Identicon from '@polkadot/ui-identicon';
|
||||||
|
|
||||||
|
import Box from './Box';
|
||||||
|
import defaults from './defaults';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
address?: string | null,
|
||||||
|
children?: React.ReactNode;
|
||||||
|
className?: string,
|
||||||
|
isHidden?: boolean,
|
||||||
|
name?: string | null,
|
||||||
|
theme?: 'polkadot' | 'substrate'
|
||||||
|
};
|
||||||
|
|
||||||
|
function Address ({ address, children, className, isHidden, name, theme = 'polkadot' }: Props) {
|
||||||
|
if (isHidden) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={className}>
|
||||||
|
<div>
|
||||||
|
<div className='name'>{name || '<unknown>'}</div>
|
||||||
|
<div className='address'>{address || '<unknown>'}</div>
|
||||||
|
<div className='children'>{children}</div>
|
||||||
|
<Identicon
|
||||||
|
className='icon'
|
||||||
|
size={64}
|
||||||
|
theme={theme}
|
||||||
|
value={address}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styled(Address)`
|
||||||
|
background: transparent;
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: ${defaults.boxPadding};
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.address,
|
||||||
|
.name {
|
||||||
|
padding-left: 4.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address {
|
||||||
|
opacity: 0.5;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: -0.75rem;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
31
packages/extension-ui/src/components/Box.tsx
Normal file
31
packages/extension-ui/src/components/Box.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import defaults from './defaults';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Box ({ children, className }: Props) {
|
||||||
|
return (
|
||||||
|
<article className={className}>
|
||||||
|
{children}
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styled(Box)`
|
||||||
|
background: ${defaults.box.info.background};
|
||||||
|
border-radius: ${defaults.borderRadius};
|
||||||
|
color: ${defaults.color};
|
||||||
|
font-family: ${defaults.fontFamily};
|
||||||
|
font-size: ${defaults.fontSize};
|
||||||
|
margin: ${defaults.boxMargin};
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
`;
|
||||||
84
packages/extension-ui/src/components/Button.tsx
Normal file
84
packages/extension-ui/src/components/Button.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import defaults from './defaults';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
children?: React.ReactNode,
|
||||||
|
isDisabled?: boolean,
|
||||||
|
isFull?: boolean,
|
||||||
|
label?: string,
|
||||||
|
onClick?: () => void,
|
||||||
|
to?: string
|
||||||
|
};
|
||||||
|
|
||||||
|
const DISABLED_OPACITY = '0.3';
|
||||||
|
|
||||||
|
function Button ({ children, className, isDisabled, label, onClick, to }: Props) {
|
||||||
|
const _onClick = (): void => {
|
||||||
|
if (isDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick && onClick();
|
||||||
|
|
||||||
|
if (to) {
|
||||||
|
window.location.hash = to;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<button onClick={_onClick}>
|
||||||
|
{label}{children}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styled(Button)`
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: ${({ isFull }) =>
|
||||||
|
isFull
|
||||||
|
? 'block'
|
||||||
|
: 'inline-block'
|
||||||
|
};
|
||||||
|
margin: ${defaults.boxMargin};
|
||||||
|
padding: ${defaults.boxPadding};
|
||||||
|
width: ${({ isFull }) =>
|
||||||
|
isFull
|
||||||
|
? '100%'
|
||||||
|
: 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: ${defaults.btnBg};
|
||||||
|
border: ${defaults.btnBorder};
|
||||||
|
border-radius: ${defaults.borderRadius};
|
||||||
|
color: ${defaults.btnColor};
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
font-size: ${defaults.fontSize};
|
||||||
|
opacity: ${({ isDisabled }) =>
|
||||||
|
isDisabled
|
||||||
|
? DISABLED_OPACITY
|
||||||
|
: '0.8'
|
||||||
|
};
|
||||||
|
padding: ${defaults.btnPadding};
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: ${({ isDisabled }) =>
|
||||||
|
isDisabled
|
||||||
|
? DISABLED_OPACITY
|
||||||
|
: '1.0'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
33
packages/extension-ui/src/components/Header.tsx
Normal file
33
packages/extension-ui/src/components/Header.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import defaults from './defaults';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children?: React.ReactNode,
|
||||||
|
className?: string,
|
||||||
|
label?: string
|
||||||
|
};
|
||||||
|
|
||||||
|
function Header ({ children, className, label }: Props) {
|
||||||
|
return (
|
||||||
|
<h2 className={className}>
|
||||||
|
{label}{children}
|
||||||
|
</h2>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styled(Header)`
|
||||||
|
background: ${defaults.hdrBg};
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: ${defaults.hdrColor};
|
||||||
|
font-family: ${defaults.fontFamily};
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 0 -0.75rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
`;
|
||||||
77
packages/extension-ui/src/components/Input.tsx
Normal file
77
packages/extension-ui/src/components/Input.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import Label from './Label';
|
||||||
|
import defaults from './defaults';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string,
|
||||||
|
isError?: boolean,
|
||||||
|
isFocussed?: boolean,
|
||||||
|
isReadOnly?: boolean,
|
||||||
|
label: string,
|
||||||
|
onChange?: (value: string) => void,
|
||||||
|
type?: 'text' | 'password',
|
||||||
|
value?: string
|
||||||
|
};
|
||||||
|
|
||||||
|
function Input ({ className, label, isFocussed, isReadOnly, onChange, type = 'text', value }: Props) {
|
||||||
|
const _onChange = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
onChange && onChange(value.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Label
|
||||||
|
className={className}
|
||||||
|
label={label}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
autoFocus={isFocussed}
|
||||||
|
readOnly={isReadOnly}
|
||||||
|
onChange={_onChange}
|
||||||
|
type={type}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styled(Input)`
|
||||||
|
input {
|
||||||
|
background: ${({ isError, isReadOnly }) =>
|
||||||
|
isError
|
||||||
|
? defaults.box.error.background
|
||||||
|
: isReadOnly
|
||||||
|
? '#eee'
|
||||||
|
: '#fff'
|
||||||
|
};
|
||||||
|
border-color: ${({ isError }) =>
|
||||||
|
isError
|
||||||
|
? defaults.box.error.border
|
||||||
|
: defaults.inputBorder
|
||||||
|
};
|
||||||
|
border-radius: ${defaults.borderRadius};
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: ${({ isError }) =>
|
||||||
|
isError
|
||||||
|
? defaults.box.error.border
|
||||||
|
: defaults.color
|
||||||
|
};
|
||||||
|
display: block;
|
||||||
|
font-family: ${defaults.fontFamily};
|
||||||
|
font-size: ${defaults.fontSize};
|
||||||
|
padding: ${defaults.inputPadding};
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:read-only {
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
39
packages/extension-ui/src/components/Label.tsx
Normal file
39
packages/extension-ui/src/components/Label.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import defaults from './defaults';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactNode,
|
||||||
|
className?: string,
|
||||||
|
label: string
|
||||||
|
};
|
||||||
|
|
||||||
|
function Label ({ children, className, label }: Props) {
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<label>{label}</label>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styled(Label)`
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: ${defaults.colorLabel};
|
||||||
|
display: block;
|
||||||
|
font-family: ${defaults.fontFamily};
|
||||||
|
font-size: ${defaults.fontSize};
|
||||||
|
margin: ${defaults.boxMargin};
|
||||||
|
padding: ${defaults.boxPadding};
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
21
packages/extension-ui/src/components/Loading.tsx
Normal file
21
packages/extension-ui/src/components/Loading.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children?: React.ReactNode
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Loading ({ children }: Props) {
|
||||||
|
if (!children) {
|
||||||
|
return (
|
||||||
|
<div>... loading ...</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>{children}</>
|
||||||
|
);
|
||||||
|
}
|
||||||
76
packages/extension-ui/src/components/TextArea.tsx
Normal file
76
packages/extension-ui/src/components/TextArea.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import Label from './Label';
|
||||||
|
import defaults from './defaults';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string,
|
||||||
|
isError?: boolean,
|
||||||
|
isFocussed?: boolean,
|
||||||
|
isReadOnly?: boolean,
|
||||||
|
label: string,
|
||||||
|
onChange?: (value: string) => void,
|
||||||
|
value?: string
|
||||||
|
};
|
||||||
|
|
||||||
|
function TextArea ({ className, isFocussed, isReadOnly, label, onChange, value }: Props) {
|
||||||
|
const _onChange = ({ target: { value } }: React.ChangeEvent<HTMLTextAreaElement>): void => {
|
||||||
|
onChange && onChange(value.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Label
|
||||||
|
className={className}
|
||||||
|
label={label}
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
autoFocus={isFocussed}
|
||||||
|
onChange={_onChange}
|
||||||
|
readOnly={isReadOnly}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styled(TextArea)`
|
||||||
|
textarea {
|
||||||
|
background: ${({ isError, isReadOnly }) =>
|
||||||
|
isError
|
||||||
|
? defaults.box.error.background
|
||||||
|
: isReadOnly
|
||||||
|
? '#eee'
|
||||||
|
: '#fff'
|
||||||
|
};
|
||||||
|
border-color: ${({ isError }) =>
|
||||||
|
isError
|
||||||
|
? defaults.box.error.border
|
||||||
|
: defaults.inputBorder
|
||||||
|
};
|
||||||
|
border-radius: ${defaults.borderRadius};
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: ${({ isError }) =>
|
||||||
|
isError
|
||||||
|
? defaults.box.error.border
|
||||||
|
: defaults.color
|
||||||
|
};
|
||||||
|
display: block;
|
||||||
|
font-family: ${defaults.fontFamily};
|
||||||
|
font-size: ${defaults.fontSize};
|
||||||
|
padding: ${defaults.inputPadding};
|
||||||
|
resize: none;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:read-only {
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
57
packages/extension-ui/src/components/Tip.tsx
Normal file
57
packages/extension-ui/src/components/Tip.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import defaults from './defaults';
|
||||||
|
|
||||||
|
type Color = {
|
||||||
|
background: string,
|
||||||
|
border: string,
|
||||||
|
color: string
|
||||||
|
};
|
||||||
|
|
||||||
|
type Type = keyof typeof defaults.box;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactNode,
|
||||||
|
className?: string,
|
||||||
|
header?: React.ReactNode,
|
||||||
|
type?: Type
|
||||||
|
};
|
||||||
|
|
||||||
|
function getColor ({ type }: Props): Color {
|
||||||
|
return defaults.box[type || 'info'] || defaults.box.info;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Tip ({ children, className, header }: Props) {
|
||||||
|
return (
|
||||||
|
<article className={className}>
|
||||||
|
{header && <h3>{header}</h3>}
|
||||||
|
<div>{children}</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styled(Tip)`
|
||||||
|
background: ${(props) =>
|
||||||
|
getColor(props).background
|
||||||
|
};
|
||||||
|
border-left: 0.25rem solid ${(props) =>
|
||||||
|
getColor(props).border
|
||||||
|
};
|
||||||
|
color: ${(props) =>
|
||||||
|
getColor(props).color
|
||||||
|
};
|
||||||
|
margin: ${defaults.boxMargin};
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: ${(props) =>
|
||||||
|
getColor(props).border
|
||||||
|
};
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
`;
|
||||||
48
packages/extension-ui/src/components/View.tsx
Normal file
48
packages/extension-ui/src/components/View.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import defaults from './defaults';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function View ({ children, className }: Props) {
|
||||||
|
return (
|
||||||
|
<main className={className}>
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styled(View)`
|
||||||
|
color: ${defaults.color};
|
||||||
|
font-family: ${defaults.fontFamily};
|
||||||
|
font-size: ${defaults.fontSize};
|
||||||
|
line-height: ${defaults.lineHeight};
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: ${defaults.linkColor};
|
||||||
|
opacity: 0.9;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: ${defaults.linkColor};
|
||||||
|
opacity: 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:visited {
|
||||||
|
color: ${defaults.linkColor};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
`;
|
||||||
51
packages/extension-ui/src/components/defaults.ts
Normal file
51
packages/extension-ui/src/components/defaults.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
const TEXT_COLOR = '#4e4e4e';
|
||||||
|
const LABEL_COLOR = 'rgba(78, 78, 78, 0.75)';
|
||||||
|
const LINK_COLOR = '#3367d6';
|
||||||
|
|
||||||
|
const defaults: { [index: string]: any } = {
|
||||||
|
borderRadius: '0.25rem',
|
||||||
|
btnBg: '#fff',
|
||||||
|
btnBorder: `2px solid ${LINK_COLOR}`,
|
||||||
|
btnColor: LINK_COLOR, // '#fefefe',
|
||||||
|
btnPadding: '0.75rem 1rem',
|
||||||
|
boxMargin: '0.75rem 0',
|
||||||
|
boxPadding: '0 0.25rem',
|
||||||
|
color: TEXT_COLOR,
|
||||||
|
colorLabel: LABEL_COLOR,
|
||||||
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif',
|
||||||
|
fontSize: '1rem',
|
||||||
|
hdrBg: 'transparent',
|
||||||
|
hdrColor: '#ccc',
|
||||||
|
inputBorder: '#ccc',
|
||||||
|
inputPadding: '0.5rem 0.75rem',
|
||||||
|
lineHeight: '1.25',
|
||||||
|
linkColor: LINK_COLOR,
|
||||||
|
box: {
|
||||||
|
error: {
|
||||||
|
background: '#ffe6e6',
|
||||||
|
border: '#c00',
|
||||||
|
color: '#4d0000'
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
background: '#fafafa',
|
||||||
|
border: TEXT_COLOR,
|
||||||
|
color: 'inherit'
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
background: '#f3f5f7',
|
||||||
|
border: '#42b983',
|
||||||
|
color: 'inherit'
|
||||||
|
},
|
||||||
|
warn: {
|
||||||
|
background: 'rgba(255, 229, 100, .3)',
|
||||||
|
border: '#e7c000',
|
||||||
|
color: '#6b5900'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defaults;
|
||||||
17
packages/extension-ui/src/components/index.ts
Normal file
17
packages/extension-ui/src/components/index.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
export { default as ActionBar } from './ActionBar';
|
||||||
|
export { default as Address } from './Address';
|
||||||
|
export { default as Box } from './Box';
|
||||||
|
export { default as Button } from './Button';
|
||||||
|
export { default as Header } from './Header';
|
||||||
|
export { default as Input } from './Input';
|
||||||
|
export { default as Loading } from './Loading';
|
||||||
|
export { default as TextArea } from './TextArea';
|
||||||
|
export { default as Tip } from './Tip';
|
||||||
|
export { default as View } from './View';
|
||||||
|
|
||||||
|
export { default as defaults } from './defaults';
|
||||||
|
export { default as unicode } from './unicode';
|
||||||
14
packages/extension-ui/src/components/unicode.tsx
Normal file
14
packages/extension-ui/src/components/unicode.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
// via http://www.amp-what.com/unicode/search/ (in hex)
|
||||||
|
|
||||||
|
const unicode = {
|
||||||
|
BACK: '\u21E6', // ⇦
|
||||||
|
DOWN: '\u21E9', // ⇩
|
||||||
|
FWD: '\u21E8', // ⇨
|
||||||
|
UP: '\u21E7' // ⇧
|
||||||
|
};
|
||||||
|
|
||||||
|
export default unicode;
|
||||||
28
packages/extension-ui/src/createView.tsx
Normal file
28
packages/extension-ui/src/createView.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React, { Suspense } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { HashRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { View } from './components';
|
||||||
|
|
||||||
|
export default function createView (Entry: React.ComponentType<any>, rootId: string = 'root'): void {
|
||||||
|
const rootElement = document.getElementById(rootId);
|
||||||
|
|
||||||
|
if (!rootElement) {
|
||||||
|
throw new Error(`Unable to find element with id '${rootId}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Suspense fallback='...'>
|
||||||
|
<View>
|
||||||
|
<HashRouter>
|
||||||
|
<Entry />
|
||||||
|
</HashRouter>
|
||||||
|
</View>
|
||||||
|
</Suspense>,
|
||||||
|
rootElement
|
||||||
|
);
|
||||||
|
}
|
||||||
6
packages/extension-ui/src/index.ts
Normal file
6
packages/extension-ui/src/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
export { default as createView } from './createView';
|
||||||
|
export { default as Popup } from './Popup';
|
||||||
51
packages/extension-ui/src/messaging.ts
Normal file
51
packages/extension-ui/src/messaging.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { MessageTypes, MessageExtrinsicSign } from '@polkadot/extension/background/types';
|
||||||
|
import { KeyringJson } from '@polkadot/ui-keyring/types';
|
||||||
|
import { KeypairType } from '@polkadot/util-crypto/types';
|
||||||
|
|
||||||
|
let idCounter = 0;
|
||||||
|
|
||||||
|
function sendMessage (message: MessageTypes, request: any = {}): Promise<any> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const id = ++idCounter;
|
||||||
|
|
||||||
|
chrome.runtime.sendMessage({ id, message, request }, ({ error, response }) => {
|
||||||
|
if (error) {
|
||||||
|
reject(new Error(error));
|
||||||
|
} else {
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAccounts (): Promise<Array<KeyringJson>> {
|
||||||
|
return sendMessage('accounts.list');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRequests (): Promise<Array<[number, MessageExtrinsicSign, string]>> {
|
||||||
|
return sendMessage('signing.requests');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cancelRequest (id: number): Promise<boolean> {
|
||||||
|
return sendMessage('signing.cancel', { id });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function approveRequest (id: number, password: string): Promise<boolean> {
|
||||||
|
return sendMessage('signing.approve', { id, password });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createAccount (name: string, password: string, suri: string, type?: KeypairType): Promise<boolean> {
|
||||||
|
return sendMessage('accounts.create', { name, password, suri, type });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createSeed (length?: number, type?: KeypairType): Promise<{ address: string, seed: string }> {
|
||||||
|
return sendMessage('seed.create', { length, type });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateSeed (seed: string, type?: KeypairType): Promise<{ address: string, seed: string }> {
|
||||||
|
return sendMessage('seed.validate', { seed, type });
|
||||||
|
}
|
||||||
26
packages/extension-ui/src/partials/Back.tsx
Normal file
26
packages/extension-ui/src/partials/Back.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { unicode } from '../components';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string,
|
||||||
|
to?: string
|
||||||
|
};
|
||||||
|
|
||||||
|
function Back ({ className, to = '/' }: Props) {
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<Link to='/'>{unicode.BACK} Back</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styled(Back)`
|
||||||
|
padding: 0.5rem;
|
||||||
|
`;
|
||||||
38
packages/extension-ui/src/partials/Name.tsx
Normal file
38
packages/extension-ui/src/partials/Name.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { Input } from '../components';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isFocussed?: boolean,
|
||||||
|
onChange: (name: string | null) => void
|
||||||
|
};
|
||||||
|
|
||||||
|
const MIN_LENGTH = 3;
|
||||||
|
|
||||||
|
export default function Name ({ isFocussed, onChange }: Props) {
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onChange(
|
||||||
|
(name.length >= MIN_LENGTH)
|
||||||
|
? name
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
}, [name]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Input
|
||||||
|
isError={name.length < MIN_LENGTH}
|
||||||
|
isFocussed={isFocussed}
|
||||||
|
label='a descriptive name for this account'
|
||||||
|
onChange={setName}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
47
packages/extension-ui/src/partials/Password.tsx
Normal file
47
packages/extension-ui/src/partials/Password.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { Input } from '../components';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isFocussed?: boolean,
|
||||||
|
onChange: (password: string | null) => void
|
||||||
|
};
|
||||||
|
|
||||||
|
const MIN_LENGTH = 6;
|
||||||
|
|
||||||
|
export default function Password ({ isFocussed, onChange }: Props) {
|
||||||
|
const [pass1, setPass1] = useState('');
|
||||||
|
const [pass2, setPass2] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onChange(
|
||||||
|
(pass1 && pass2 && (pass1.length >= MIN_LENGTH) && (pass1 === pass2))
|
||||||
|
? pass1
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
}, [pass1, pass2]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Input
|
||||||
|
isError={pass1.length < MIN_LENGTH}
|
||||||
|
isFocussed={isFocussed}
|
||||||
|
label='a new password for this account'
|
||||||
|
onChange={setPass1}
|
||||||
|
type='password'
|
||||||
|
/>
|
||||||
|
{(pass1.length >= MIN_LENGTH) && (
|
||||||
|
<Input
|
||||||
|
isError={pass1 !== pass2}
|
||||||
|
label='repeat password for verification'
|
||||||
|
onChange={setPass2}
|
||||||
|
type='password'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
packages/extension-ui/src/partials/index.ts
Normal file
7
packages/extension-ui/src/partials/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
export { default as Back } from './Back';
|
||||||
|
export { default as Name } from './Name';
|
||||||
|
export { default as Password } from './Password';
|
||||||
19
packages/extension/package.json
Normal file
19
packages/extension/package.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@polkadot/extension",
|
||||||
|
"description": "A sample signer extension for the @polkadot/api",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||||
|
"license": "Apache-2",
|
||||||
|
"dependencies": {
|
||||||
|
"@polkadot/api": "^0.79.0-beta.31",
|
||||||
|
"@polkadot/extension-inject": "^0.1.0",
|
||||||
|
"@polkadot/extension-ui": "^0.1.0",
|
||||||
|
"@polkadot/ui-keyring": "^0.39.0-beta.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/chrome": "^0.0.85",
|
||||||
|
"extract-loader": "^3.1.0",
|
||||||
|
"file-loader": "^3.0.1",
|
||||||
|
"html-loader": "^0.5.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
packages/extension/public/favicon.ico
Normal file
BIN
packages/extension/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
BIN
packages/extension/public/images/icon-128.png
Normal file
BIN
packages/extension/public/images/icon-128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 334 B |
BIN
packages/extension/public/images/icon-16.png
Normal file
BIN
packages/extension/public/images/icon-16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 160 B |
BIN
packages/extension/public/images/icon-32.png
Normal file
BIN
packages/extension/public/images/icon-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 177 B |
BIN
packages/extension/public/images/icon-48.png
Normal file
BIN
packages/extension/public/images/icon-48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 193 B |
BIN
packages/extension/public/images/icon-64.png
Normal file
BIN
packages/extension/public/images/icon-64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 215 B |
31
packages/extension/public/manifest.json
Normal file
31
packages/extension/public/manifest.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "polkadot{.js} extension",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"manifest_version": 2,
|
||||||
|
"permissions": ["activeTab", "storage"],
|
||||||
|
"background": {
|
||||||
|
"scripts": ["background.js"],
|
||||||
|
"persistent": true
|
||||||
|
},
|
||||||
|
"browser_action": {
|
||||||
|
"default_title": "polkadot{.js}",
|
||||||
|
"default_popup": "popup.html"
|
||||||
|
},
|
||||||
|
"content_scripts": [{
|
||||||
|
"js": ["loader.js"],
|
||||||
|
"matches": [
|
||||||
|
"https://polkadot.js.org/apps/*",
|
||||||
|
"http://localhost:3000/*"
|
||||||
|
],
|
||||||
|
"run_at": "document_start"
|
||||||
|
}],
|
||||||
|
"icons": {
|
||||||
|
"16": "images/icon-16.png",
|
||||||
|
"32": "images/icon-32.png",
|
||||||
|
"48": "images/icon-48.png",
|
||||||
|
"64": "images/icon-64.png",
|
||||||
|
"128": "images/icon-128.png"
|
||||||
|
},
|
||||||
|
"web_accessible_resources": ["inject.js"],
|
||||||
|
"content_security_policy": "script-src 'self' 'unsafe-eval' 'wasm-eval'; object-src 'self'"
|
||||||
|
}
|
||||||
11
packages/extension/public/popup.html
Normal file
11
packages/extension/public/popup.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>polkadot{.js}</title>
|
||||||
|
</head>
|
||||||
|
<body style="border-top:0.25rem solid #ccc; height:640px; margin:0; padding:0 0.75rem 0.75rem; width:480px;">
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src='./popup.js'></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
56
packages/extension/src/background/ChromeStore.ts
Normal file
56
packages/extension/src/background/ChromeStore.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-bg authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { KeyringStore } from '@polkadot/ui-keyring/types';
|
||||||
|
|
||||||
|
const lastError = (type: string): void => {
|
||||||
|
const error = chrome.runtime.lastError;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error(`ChromeStore.${type}:: chrome.runtime.lastError:`, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class ChromeStore implements KeyringStore {
|
||||||
|
all (cb: (key: string, value: any) => void): void {
|
||||||
|
chrome.storage.local.get(null, (result: { [index: string]: any }) => {
|
||||||
|
lastError('all');
|
||||||
|
|
||||||
|
Object.entries(result).forEach(([key, value]) =>
|
||||||
|
cb(key, value)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get (key: string, cb: (value: any) => void): void {
|
||||||
|
chrome.storage.local.get([key], (result: { [index: string]: any }) => {
|
||||||
|
lastError('get');
|
||||||
|
|
||||||
|
cb(result[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
remove (key: string, cb?: () => void): void {
|
||||||
|
chrome.storage.local.remove(key, () => {
|
||||||
|
lastError('remove');
|
||||||
|
|
||||||
|
cb && cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
set (key: string, value: any, cb?: () => void): void {
|
||||||
|
// shortcut, don't save testing accounts in storage
|
||||||
|
if (key.indexOf('account:') === 0 && value.meta && value.meta.isTesting) {
|
||||||
|
cb && cb();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.storage.local.set({ [key]: value }, () => {
|
||||||
|
lastError('set');
|
||||||
|
|
||||||
|
cb && cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
83
packages/extension/src/background/RawPayload.ts
Normal file
83
packages/extension/src/background/RawPayload.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/types authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { KeyringPair } from '@polkadot/keyring/types';
|
||||||
|
|
||||||
|
import { ExtrinsicEra, Hash, IndexCompact, Struct, U8a } from '@polkadot/types';
|
||||||
|
import { blake2AsU8a } from '@polkadot/util-crypto';
|
||||||
|
|
||||||
|
// This is a copy of the signing in @polkadot/types with the method as a pure U8a
|
||||||
|
// (without metadata, we cannot reliably parse the Method, hence faling)
|
||||||
|
export default class SignaturePayloadRaw extends Struct {
|
||||||
|
protected _signature?: Uint8Array;
|
||||||
|
|
||||||
|
constructor (value?: any) {
|
||||||
|
super({
|
||||||
|
nonce: IndexCompact,
|
||||||
|
method: U8a,
|
||||||
|
era: ExtrinsicEra,
|
||||||
|
blockHash: Hash
|
||||||
|
}, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description `true` if the payload refers to a valid signature
|
||||||
|
*/
|
||||||
|
get isSigned (): boolean {
|
||||||
|
return !!(this._signature && this._signature.length === 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description The block [[Hash]] the signature applies to (mortal/immortal)
|
||||||
|
*/
|
||||||
|
get blockHash (): Hash {
|
||||||
|
return this.get('blockHash') as Hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description The [[Method]] contained in the payload
|
||||||
|
*/
|
||||||
|
get method (): U8a {
|
||||||
|
return this.get('method') as U8a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description The [[ExtrinsicEra]]
|
||||||
|
*/
|
||||||
|
get era (): ExtrinsicEra {
|
||||||
|
return this.get('era') as ExtrinsicEra;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description The [[IndexCompact]]
|
||||||
|
*/
|
||||||
|
get nonce (): IndexCompact {
|
||||||
|
return this.get('nonce') as IndexCompact;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description The raw signature as a `Uint8Array`
|
||||||
|
*/
|
||||||
|
get signature (): Uint8Array {
|
||||||
|
if (!this.isSigned) {
|
||||||
|
throw new Error('Transaction is not signed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._signature as Uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Sign the payload with the keypair
|
||||||
|
*/
|
||||||
|
sign (signerPair: KeyringPair): Uint8Array {
|
||||||
|
const u8a = this.toU8a();
|
||||||
|
const encoded = u8a.length > 256
|
||||||
|
? blake2AsU8a(u8a)
|
||||||
|
: u8a;
|
||||||
|
|
||||||
|
this._signature = signerPair.sign(encoded);
|
||||||
|
|
||||||
|
return this._signature;
|
||||||
|
}
|
||||||
|
}
|
||||||
129
packages/extension/src/background/handlers/Extension.ts
Normal file
129
packages/extension/src/background/handlers/Extension.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-bg authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { KeyringJson } from '@polkadot/ui-keyring/types';
|
||||||
|
import { MessageTypes, MessageAccountCreate, MessageExtrinsicSign, MessageExtrinsicSignApprove, MessageExtrinsicSignCancel, MessageSeedCreate, MessageSeedCreate$Response, MessageSeedValidate, MessageSeedValidate$Response } from '../types';
|
||||||
|
|
||||||
|
import keyring from '@polkadot/ui-keyring';
|
||||||
|
import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
|
||||||
|
import { mnemonicGenerate, mnemonicValidate } from '@polkadot/util-crypto';
|
||||||
|
import { assert, u8aToHex } from '@polkadot/util';
|
||||||
|
|
||||||
|
import RawPayload from '../RawPayload';
|
||||||
|
import SigningRequests from './SigningRequests';
|
||||||
|
|
||||||
|
const SEED_DEFAULT_LENGTH = 12;
|
||||||
|
const SEED_LENGTHS = [12, 24];
|
||||||
|
|
||||||
|
export default class Extension {
|
||||||
|
private signing: SigningRequests;
|
||||||
|
|
||||||
|
constructor (signing: SigningRequests) {
|
||||||
|
this.signing = signing;
|
||||||
|
}
|
||||||
|
|
||||||
|
private accountsCreate ({ name, password, suri, type }: MessageAccountCreate): boolean {
|
||||||
|
keyring.addUri(suri, password, { name }, type);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private accountsList (): Array<KeyringJson> {
|
||||||
|
return Object
|
||||||
|
.values(accountsObservable.subject.getValue())
|
||||||
|
.map(({ json }) => json);
|
||||||
|
}
|
||||||
|
|
||||||
|
private seedCreate ({ length = SEED_DEFAULT_LENGTH, type }: MessageSeedCreate): MessageSeedCreate$Response {
|
||||||
|
const seed = mnemonicGenerate(length);
|
||||||
|
|
||||||
|
return {
|
||||||
|
address: keyring.createFromUri(seed, {}, type).address(),
|
||||||
|
seed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private seedValidate ({ seed, type }: MessageSeedValidate): MessageSeedValidate$Response {
|
||||||
|
assert(SEED_LENGTHS.includes(seed.split(' ').length), `Mnemonic needs to contain ${SEED_LENGTHS.join(', ')} words`);
|
||||||
|
assert(mnemonicValidate(seed), 'Not a valid mnemonic seed');
|
||||||
|
|
||||||
|
return {
|
||||||
|
address: keyring.createFromUri(seed, {}, type).address(),
|
||||||
|
seed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private signingApprove ({ id, password }: MessageExtrinsicSignApprove): boolean {
|
||||||
|
const queued = this.signing.get(id);
|
||||||
|
|
||||||
|
assert(queued, 'Unable to find request');
|
||||||
|
|
||||||
|
const { request: { address, blockHash, method, nonce }, resolve, reject } = queued;
|
||||||
|
const pair = keyring.getPair(address);
|
||||||
|
|
||||||
|
if (!pair) {
|
||||||
|
reject(new Error('Unable to find pair'));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pair.decodePkcs8(password);
|
||||||
|
|
||||||
|
const payload = new RawPayload({ blockHash, method, nonce });
|
||||||
|
const signature = u8aToHex(payload.sign(pair));
|
||||||
|
|
||||||
|
pair.lock();
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
id,
|
||||||
|
signature
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private signingCancel ({ id }: MessageExtrinsicSignCancel): boolean {
|
||||||
|
const queued = this.signing.get(id);
|
||||||
|
|
||||||
|
assert(queued, 'Unable to find request');
|
||||||
|
|
||||||
|
const { reject } = queued;
|
||||||
|
|
||||||
|
reject(new Error('Cancelled'));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private signingRequests (): Array<[number, MessageExtrinsicSign, string]> {
|
||||||
|
return this.signing.requests;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle (type: MessageTypes, request: any): Promise<any> {
|
||||||
|
switch (type) {
|
||||||
|
case 'accounts.create':
|
||||||
|
return this.accountsCreate(request);
|
||||||
|
|
||||||
|
case 'accounts.list':
|
||||||
|
return this.accountsList();
|
||||||
|
|
||||||
|
case 'seed.create':
|
||||||
|
return this.seedCreate(request);
|
||||||
|
|
||||||
|
case 'seed.validate':
|
||||||
|
return this.seedValidate(request);
|
||||||
|
|
||||||
|
case 'signing.approve':
|
||||||
|
return this.signingApprove(request);
|
||||||
|
|
||||||
|
case 'signing.cancel':
|
||||||
|
return this.signingCancel(request);
|
||||||
|
|
||||||
|
case 'signing.requests':
|
||||||
|
return this.signingRequests();
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unable to handle message of type ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-bg authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { MessageExtrinsicSign, MessageExtrinsicSign$Response } from '../types';
|
||||||
|
|
||||||
|
type Request = {
|
||||||
|
id: number,
|
||||||
|
request: MessageExtrinsicSign,
|
||||||
|
resolve: (result: MessageExtrinsicSign$Response) => void,
|
||||||
|
reject: (error: Error) => void,
|
||||||
|
url: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class SigningRequests {
|
||||||
|
private _requests: { [index: number]: Request } = {};
|
||||||
|
|
||||||
|
get hasRequests (): boolean {
|
||||||
|
return this.numRequests === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get numRequests (): number {
|
||||||
|
return Object.keys(this._requests).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
get requests (): Array<[number, MessageExtrinsicSign, string]> {
|
||||||
|
return Object
|
||||||
|
.values(this._requests)
|
||||||
|
.map(({ id, request, url }) => [id, request, url]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private complete = (id: number, fn: Function) => {
|
||||||
|
return (result: MessageExtrinsicSign$Response | Error): void => {
|
||||||
|
delete this._requests[id];
|
||||||
|
this.updateIcon();
|
||||||
|
|
||||||
|
fn(result);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateIcon (): void {
|
||||||
|
const count = this.numRequests;
|
||||||
|
|
||||||
|
chrome.browserAction.setBadgeText({ text: count ? `${this.numRequests}` : '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
get (id: number): Request {
|
||||||
|
return this._requests[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
queue (id: number, request: MessageExtrinsicSign, url: string): Promise<MessageExtrinsicSign$Response> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this._requests[id] = {
|
||||||
|
id,
|
||||||
|
request,
|
||||||
|
resolve: this.complete(id, resolve),
|
||||||
|
reject: this.complete(id, reject),
|
||||||
|
url
|
||||||
|
};
|
||||||
|
|
||||||
|
this.updateIcon();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
52
packages/extension/src/background/handlers/Tabs.ts
Normal file
52
packages/extension/src/background/handlers/Tabs.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-bg authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { MessageTypes, MessageExtrinsicSign, MessageExtrinsicSign$Response } from '../types';
|
||||||
|
|
||||||
|
import keyring from '@polkadot/ui-keyring';
|
||||||
|
import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
|
||||||
|
import { assert } from '@polkadot/util';
|
||||||
|
|
||||||
|
import SigningRequests from './SigningRequests';
|
||||||
|
|
||||||
|
let signCounter = 0;
|
||||||
|
|
||||||
|
export default class Tabs {
|
||||||
|
private signing: SigningRequests;
|
||||||
|
|
||||||
|
constructor (signing: SigningRequests) {
|
||||||
|
this.signing = signing;
|
||||||
|
}
|
||||||
|
|
||||||
|
private accountsList (): Array<{ address: string, name?: string }> {
|
||||||
|
return Object
|
||||||
|
.values(accountsObservable.subject.getValue())
|
||||||
|
.map(({ json: { address, meta: { name } } }) => ({
|
||||||
|
address, name
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private extrinsicSign (request: MessageExtrinsicSign, url: string): Promise<MessageExtrinsicSign$Response> {
|
||||||
|
const { address } = request;
|
||||||
|
const id = ++signCounter;
|
||||||
|
const pair = keyring.getPair(address);
|
||||||
|
|
||||||
|
assert(pair, 'Unable to find keypair');
|
||||||
|
|
||||||
|
return this.signing.queue(id, request, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle (type: MessageTypes, request: any, url: string = 'unknown'): Promise<any> {
|
||||||
|
switch (type) {
|
||||||
|
case 'accounts.list':
|
||||||
|
return this.accountsList();
|
||||||
|
|
||||||
|
case 'extrinsic.sign':
|
||||||
|
return this.extrinsicSign(request, url);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unable to handle message of type ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
packages/extension/src/background/handlers/index.ts
Normal file
38
packages/extension/src/background/handlers/index.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-bg authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { MessageRequest } from '../types';
|
||||||
|
|
||||||
|
import Extension from './Extension';
|
||||||
|
import SigningRequests from './SigningRequests';
|
||||||
|
import Tabs from './Tabs';
|
||||||
|
|
||||||
|
const signing = new SigningRequests();
|
||||||
|
const extension = new Extension(signing);
|
||||||
|
const tabs = new Tabs(signing);
|
||||||
|
|
||||||
|
export default function handler ({ id, message, request }: MessageRequest, { tab }: chrome.runtime.MessageSender, sendResponse: (response?: any) => void): boolean {
|
||||||
|
const source = `${tab ? tab.url : 'extension'}: ${id}: ${message}`;
|
||||||
|
|
||||||
|
console.log(` [in] ${source}`); // :: ${JSON.stringify(request)}`);
|
||||||
|
|
||||||
|
const promise = tab
|
||||||
|
? tabs.handle(message, request, tab.url)
|
||||||
|
: extension.handle(message, request);
|
||||||
|
|
||||||
|
promise
|
||||||
|
.then((response) => {
|
||||||
|
console.log(`[out] ${source}`); // :: ${JSON.stringify(response)}`);
|
||||||
|
|
||||||
|
sendResponse({ id, response });
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(`[err] ${source}:: ${error.message}`);
|
||||||
|
|
||||||
|
sendResponse({ id, error: error.message });
|
||||||
|
});
|
||||||
|
|
||||||
|
// return true to indicate we are sending the respionse async
|
||||||
|
return true;
|
||||||
|
}
|
||||||
27
packages/extension/src/background/index.ts
Normal file
27
packages/extension/src/background/index.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-bg authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
// Runs in the extension background, handling all keyring access
|
||||||
|
|
||||||
|
import keyring from '@polkadot/ui-keyring';
|
||||||
|
import { cryptoWaitReady } from '@polkadot/util-crypto';
|
||||||
|
|
||||||
|
import ChromeStore from './ChromeStore';
|
||||||
|
import handlers from './handlers';
|
||||||
|
|
||||||
|
chrome.browserAction.setBadgeBackgroundColor({ color: '#ff0000' });
|
||||||
|
chrome.runtime.onMessage.addListener(handlers);
|
||||||
|
|
||||||
|
cryptoWaitReady()
|
||||||
|
.then(() => {
|
||||||
|
console.log('crypto initialized');
|
||||||
|
|
||||||
|
// load all the keyring data
|
||||||
|
keyring.loadAll({ store: new ChromeStore(), type: 'sr25519' });
|
||||||
|
|
||||||
|
console.log('initialization completed');
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('initialization failed', error);
|
||||||
|
});
|
||||||
67
packages/extension/src/background/types.ts
Normal file
67
packages/extension/src/background/types.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-bg authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { KeypairType } from '@polkadot/util-crypto/types';
|
||||||
|
|
||||||
|
export type MessageTypes = 'accounts.create' | 'accounts.list' | 'extrinsic.sign' | 'seed.create' | 'seed.validate' | 'signing.approve' | 'signing.cancel' | 'signing.requests';
|
||||||
|
|
||||||
|
export type MessageRequest = {
|
||||||
|
id: number,
|
||||||
|
message: MessageTypes,
|
||||||
|
request: any
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MessageResponse = {
|
||||||
|
error?: string,
|
||||||
|
id: number,
|
||||||
|
response?: any
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MessageAccountCreate = {
|
||||||
|
name: string,
|
||||||
|
password: string,
|
||||||
|
suri: string,
|
||||||
|
type?: KeypairType
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MessageExtrinsicSignApprove = {
|
||||||
|
id: number,
|
||||||
|
password: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MessageExtrinsicSignCancel = {
|
||||||
|
id: number
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MessageExtrinsicSign = {
|
||||||
|
address: string,
|
||||||
|
blockHash: string,
|
||||||
|
method: string,
|
||||||
|
nonce: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MessageExtrinsicSign$Response = {
|
||||||
|
id: number,
|
||||||
|
signature: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MessageSeedCreate$Response = {
|
||||||
|
address: string,
|
||||||
|
seed: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MessageSeedCreate = {
|
||||||
|
length?: 12 | 24,
|
||||||
|
type?: KeypairType
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MessageSeedValidate = {
|
||||||
|
seed: string,
|
||||||
|
type?: KeypairType
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MessageSeedValidate$Response = {
|
||||||
|
address: string,
|
||||||
|
seed: string
|
||||||
|
};
|
||||||
10
packages/extension/src/events.ts
Normal file
10
packages/extension/src/events.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-bg authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
const events = {
|
||||||
|
request: 'PolkadotJsWebRequest',
|
||||||
|
response: 'PolkadotJsWebResponse'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default events;
|
||||||
17
packages/extension/src/inject/Accounts.ts
Normal file
17
packages/extension/src/inject/Accounts.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-inject authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Accounts as IAccounts, Account as IAccount, SendRequest } from './types';
|
||||||
|
|
||||||
|
let sendRequest: SendRequest;
|
||||||
|
|
||||||
|
export default class Accounts implements IAccounts {
|
||||||
|
constructor (_sendRequest: SendRequest) {
|
||||||
|
sendRequest = _sendRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
get (): Promise<Array<IAccount>> {
|
||||||
|
return sendRequest('accounts.list');
|
||||||
|
}
|
||||||
|
}
|
||||||
22
packages/extension/src/inject/Injected.ts
Normal file
22
packages/extension/src/inject/Injected.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-inject authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Injected as IInjected, SendRequest, Version } from './types';
|
||||||
|
|
||||||
|
import Accounts from './Accounts';
|
||||||
|
import Signer from './Signer';
|
||||||
|
|
||||||
|
export default class Injected implements IInjected {
|
||||||
|
readonly accounts: Accounts;
|
||||||
|
readonly signer: Signer;
|
||||||
|
|
||||||
|
constructor (sendRequest: SendRequest) {
|
||||||
|
this.accounts = new Accounts(sendRequest);
|
||||||
|
this.signer = new Signer(sendRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
get version (): Version {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
37
packages/extension/src/inject/Signer.ts
Normal file
37
packages/extension/src/inject/Signer.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-inject authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { SignatureOptions } from '@polkadot/types/types';
|
||||||
|
import { Signer as ISigner, SendRequest } from './types';
|
||||||
|
|
||||||
|
import { Extrinsic, Hash } from '@polkadot/types';
|
||||||
|
import { SubmittableResult } from '@polkadot/api/SubmittableExtrinsic';
|
||||||
|
|
||||||
|
let sendRequest: SendRequest;
|
||||||
|
|
||||||
|
export default class Signer implements ISigner {
|
||||||
|
constructor (_sendRequest: SendRequest) {
|
||||||
|
sendRequest = _sendRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sign (extrinsic: Extrinsic, address: string, { blockHash, nonce }: SignatureOptions): Promise<number> {
|
||||||
|
// Bit of a hack - with this round-about way, we skip any keyring deps
|
||||||
|
const cleaned = JSON.parse(JSON.stringify({
|
||||||
|
address,
|
||||||
|
blockHash,
|
||||||
|
method: extrinsic.method.toHex(),
|
||||||
|
nonce
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { id, signature } = await sendRequest('extrinsic.sign', cleaned);
|
||||||
|
|
||||||
|
extrinsic.addSignature(address as any, signature, nonce);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
update (id: number, status: Hash | SubmittableResult): void {
|
||||||
|
// something
|
||||||
|
}
|
||||||
|
}
|
||||||
45
packages/extension/src/inject/index.ts
Normal file
45
packages/extension/src/inject/index.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { WindowInjected } from './types';
|
||||||
|
|
||||||
|
import events from '../events';
|
||||||
|
import Injected from './Injected';
|
||||||
|
|
||||||
|
const callbacks: { [index: number]: { resolve: (data: any) => void, reject: (error: Error) => void } } = {};
|
||||||
|
let idCounter = 0;
|
||||||
|
|
||||||
|
// setup a response listener (events created by the loader for extension responses)
|
||||||
|
document.addEventListener(events.response, (event) => {
|
||||||
|
const response = (event as CustomEvent).detail;
|
||||||
|
const promise = callbacks[response.id];
|
||||||
|
|
||||||
|
if (!promise) {
|
||||||
|
console.error(`Uknown response: ${JSON.stringify(response)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete callbacks[response.id];
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
promise.reject(new Error(response.error));
|
||||||
|
} else {
|
||||||
|
promise.resolve(response.response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
(window as WindowInjected).injectedWeb3 = (window as WindowInjected).injectedWeb3 || {};
|
||||||
|
(window as WindowInjected).injectedWeb3['polkadot-js'] = new Injected(
|
||||||
|
(message: string, request: any = null): Promise<any> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const id = ++idCounter;
|
||||||
|
|
||||||
|
callbacks[id] = { resolve, reject };
|
||||||
|
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent(events.request, { detail: { id, message, request } })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
36
packages/extension/src/inject/types.ts
Normal file
36
packages/extension/src/inject/types.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension-inject authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Signer as SignerInterface } from '@polkadot/api/types';
|
||||||
|
|
||||||
|
export type Version = 0;
|
||||||
|
|
||||||
|
export interface Account {
|
||||||
|
readonly address: string; // ss-58 encoded address
|
||||||
|
readonly name?: string; // optional name for display
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Accounts {
|
||||||
|
get: () => Promise<Array<Account>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Signer extends SignerInterface {
|
||||||
|
// no specific signer extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Injected {
|
||||||
|
readonly accounts: Accounts;
|
||||||
|
readonly signer: Signer;
|
||||||
|
readonly version: Version;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SendRequest {
|
||||||
|
(message: string, request?: any): Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WindowInjected = Window & {
|
||||||
|
injectedWeb3: {
|
||||||
|
[index: string]: Injected
|
||||||
|
}
|
||||||
|
};
|
||||||
39
packages/extension/src/loader.ts
Normal file
39
packages/extension/src/loader.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import events from './events';
|
||||||
|
|
||||||
|
function sendResponse (detail: any): void {
|
||||||
|
document.dispatchEvent(new CustomEvent(events.response, { detail }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle all custom events, passing messages to the extension
|
||||||
|
document.addEventListener(events.request, (event) => {
|
||||||
|
// get the request as part of the the event detailt
|
||||||
|
const request = (event as CustomEvent).detail;
|
||||||
|
|
||||||
|
// pass the detail as-is as a message to the extension
|
||||||
|
chrome.runtime.sendMessage(request, (response) => {
|
||||||
|
if (!response) {
|
||||||
|
// no data, send diconnected/unknown error
|
||||||
|
sendResponse({ id: request.id, error: 'Unknown response' });
|
||||||
|
} else {
|
||||||
|
// handle the response, passing as a custom event
|
||||||
|
sendResponse(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// inject our data injector
|
||||||
|
const script = document.createElement('script');
|
||||||
|
|
||||||
|
script.src = chrome.extension.getURL('inject.js');
|
||||||
|
script.onload = (): void => {
|
||||||
|
// remove the injecting tag when loaded
|
||||||
|
if (script.parentNode) {
|
||||||
|
script.parentNode.removeChild(script);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(document.head || document.documentElement).appendChild(script);
|
||||||
7
packages/extension/src/views/popup.ts
Normal file
7
packages/extension/src/views/popup.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { createView, Popup } from '@polkadot/extension-ui';
|
||||||
|
|
||||||
|
createView(Popup);
|
||||||
110
packages/extension/webpack.config.js
Normal file
110
packages/extension/webpack.config.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2019 @polkadot/extension authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
const { WebpackPluginServe } = require('webpack-plugin-serve');
|
||||||
|
|
||||||
|
const packages = [
|
||||||
|
'extension-ui'
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_THEME = 'polkadot';
|
||||||
|
|
||||||
|
function createWebpack ({ alias = {}, context }) {
|
||||||
|
const pkgJson = require(path.join(context, 'package.json'));
|
||||||
|
const ENV = process.env.NODE_ENV || 'development';
|
||||||
|
const isProd = ENV === 'production';
|
||||||
|
|
||||||
|
return {
|
||||||
|
context,
|
||||||
|
devtool: 'source-map',
|
||||||
|
entry: {
|
||||||
|
background: './src/background/index.ts',
|
||||||
|
inject: './src/inject/index.ts',
|
||||||
|
loader: './src/loader.ts',
|
||||||
|
popup: `./src/views/popup.ts`
|
||||||
|
},
|
||||||
|
mode: ENV,
|
||||||
|
output: {
|
||||||
|
chunkFilename: `[name].js`,
|
||||||
|
filename: `[name].js`,
|
||||||
|
globalObject: `(typeof self !== 'undefined' ? self : this)`,
|
||||||
|
path: path.join(context, 'build')
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias,
|
||||||
|
extensions: ['.js', '.jsx', '.ts', '.tsx']
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(js|ts|tsx)$/,
|
||||||
|
exclude: /(node_modules)/,
|
||||||
|
use: [
|
||||||
|
require.resolve('thread-loader'),
|
||||||
|
{
|
||||||
|
loader: require.resolve('babel-loader'),
|
||||||
|
options: require('@polkadot/dev-react/config/babel')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: require.resolve('url-loader'),
|
||||||
|
options: {
|
||||||
|
limit: 10000,
|
||||||
|
name: 'static/[name].[ext]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
child_process: 'empty',
|
||||||
|
dgram: 'empty',
|
||||||
|
fs: 'empty',
|
||||||
|
net: 'empty',
|
||||||
|
tls: 'empty'
|
||||||
|
},
|
||||||
|
performance: {
|
||||||
|
hints: false
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
isProd
|
||||||
|
? null
|
||||||
|
: new WebpackPluginServe({
|
||||||
|
port: 3001,
|
||||||
|
static: path.join(process.cwd(), '/build')
|
||||||
|
}),
|
||||||
|
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env': {
|
||||||
|
NODE_ENV: JSON.stringify(ENV),
|
||||||
|
VERSION: JSON.stringify(pkgJson.version),
|
||||||
|
UI_MODE: JSON.stringify(process.env.UI_MODE || 'full'),
|
||||||
|
UI_THEME: JSON.stringify(process.env.UI_THEME || DEFAULT_THEME),
|
||||||
|
WS_URL: JSON.stringify(process.env.WS_URL)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new CopyWebpackPlugin([{ from: 'public' }])
|
||||||
|
].filter((entry) => entry),
|
||||||
|
watch: !isProd
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = createWebpack({
|
||||||
|
context: __dirname,
|
||||||
|
alias: packages.reduce((alias, pkg) => {
|
||||||
|
alias[`@polkadot/${pkg}`] = path.resolve(__dirname, `../${pkg}/src`);
|
||||||
|
|
||||||
|
return alias;
|
||||||
|
}, {})
|
||||||
|
});
|
||||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": "./node_modules/@polkadot/dev/config/tsconfig",
|
||||||
|
"compilerOptions":
|
||||||
|
{
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths":
|
||||||
|
{
|
||||||
|
"@polkadot/extension": ["packages/extension/src"],
|
||||||
|
"@polkadot/extension/*": ["packages/extension/src/*"],
|
||||||
|
"@polkadot/extension-ui": ["packages/extension-ui/src"],
|
||||||
|
"@polkadot/extension-ui/*": ["packages/extension-ui/src/*"]
|
||||||
|
},
|
||||||
|
"typeRoots": [
|
||||||
|
"./node_modules/@polkadot/ts",
|
||||||
|
"./node_modules/@types"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
9
tslint.json
Normal file
9
tslint.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "@polkadot/dev/config/tslint",
|
||||||
|
"linterOptions": {
|
||||||
|
"exclude": "src/**/*.json"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"completed-docs": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user