Initial commit aka code from heaven
This commit is contained in:
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user