Mobile signer support (via Qr) (#134)
* Add typings to messages * Fix eslint (after merge) * Mobile signer support (via Qr) * Bump common * Pass through genesisHash * Ok, well, it does something... not recognized * Bump ui * Exclude build from lint * ** * Display accounts with prefix from genesisHash * Adapth for object via scan interface * Bump deps * Split address recoding * Cleanup decoding readability * Display chain affinity overlay * -1 prefix dropdown description * Add generator command * Fix TypeScript error * Lint * Consistent naming * Better promise error handling * Small consistency updates * Constraint handle typings as much as tsc will allow * State naming * Typo in cheain descriptions * Add signature handler (unused) * e2e roundtrip * remove (useless) dev 0.5.1 chain * Insert ActionBar * Revert to empty Props interfaces * SeedLengths type * Add type to transport subscription message * nit * Typecheck passing callback to sendMessage when none expected * Bump api * Small lint fixes * Bump api * Not sure it works... but linting passes * Split public/private * Alphabetical * Don't show button on no media * Remove unneeded resolve - NOT hacking around linting * Pass edited name through to accounts component * genesisHash alignments * Remove KeyringJson * SignRequest with account * Align with master naming * Some manual merges * Manual (small) conflict resolution * Fixup extension account mapping * Remove ununsed messages (less code = better) * Update comment around generation * Add copy-to-apps script (debugging)
This commit is contained in:
parent
bf92e522f2
commit
4945417982
@ -1,3 +1,7 @@
|
||||
# 0.10.0-beta.x
|
||||
|
||||
- Support for external accounts, as presented by mobile signers, e.g. the Parity Signer
|
||||
|
||||
# 0.9.1
|
||||
|
||||
- Fix an initialization error in extension-dapp
|
||||
|
||||
@ -77,6 +77,8 @@ interface Injected {
|
||||
interface Account = {
|
||||
// ss-58 encoded address
|
||||
readonly address: string;
|
||||
// the genesisHash for this account (empty if applicable to all)
|
||||
readonly genesisHash?: string;
|
||||
// (optional) name for display
|
||||
readonly name?: string;
|
||||
};
|
||||
|
||||
16
copy-to-apps.sh
Executable file
16
copy-to-apps.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
function copy_folder () {
|
||||
SRC="packages/$1/build"
|
||||
DST="../apps/node_modules/@polkadot/$1"
|
||||
|
||||
echo "** Copying $SRC to $DST"
|
||||
|
||||
rm -rf $DST
|
||||
cp -r $SRC $DST
|
||||
}
|
||||
|
||||
yarn polkadot-dev-build-ts
|
||||
|
||||
copy_folder "extension-dapp"
|
||||
copy_folder "extension-inject"
|
||||
@ -3,7 +3,7 @@ const config = require('@polkadot/dev/config/jest');
|
||||
|
||||
module.exports = Object.assign({}, config, {
|
||||
moduleNameMapper: {
|
||||
'@polkadot/extension-(ui)(.*)$': '<rootDir>/packages/extension-$1/src/$2',
|
||||
'@polkadot/extension-(chains|dapp|inject|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'
|
||||
},
|
||||
|
||||
201
packages/extension-chains/LICENSE
Normal file
201
packages/extension-chains/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.
|
||||
3
packages/extension-chains/README.md
Normal file
3
packages/extension-chains/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# @polkadot/extension-chains
|
||||
|
||||
Definitions for chains that are supported by this extension. It contains the bare definitions as well as a stripped-down (call-only) metadata format.
|
||||
13
packages/extension-chains/package.json
Normal file
13
packages/extension-chains/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@polkadot/extension-chains",
|
||||
"description": "Definitions for all known chains as exposed by the extension.",
|
||||
"version": "0.10.0-beta.1",
|
||||
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||
"license": "Apache-2",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@polkadot/api": "*"
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,9 @@
|
||||
// Copyright 2019 @polkadot/extension-chains authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
// Generated via (in api) `yarn chain:info --ws wss://poc3-rpc.polkadot.io`
|
||||
|
||||
export default {
|
||||
chain: 'Alexander',
|
||||
genesisHash: '0xdcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b',
|
||||
40
packages/extension-chains/src/index.ts
Normal file
40
packages/extension-chains/src/index.ts
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2019 @polkadot/extension-chains 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 { Metadata } from '@polkadot/types';
|
||||
import { Chain } from './types';
|
||||
|
||||
// imports chain details, generally metadata. For the generation of these,
|
||||
// inside the api, run `yarn chain:info [--ws <url>]`
|
||||
import alexander from './alexander';
|
||||
import kusama from './kusama';
|
||||
|
||||
const chains: Map<string, Chain> = new Map(
|
||||
[alexander, kusama].map(
|
||||
({ chain, genesisHash, metaCalls, ss58Format, tokenDecimals, tokenSymbol }): [string, Chain] => [
|
||||
genesisHash,
|
||||
{
|
||||
genesisHash,
|
||||
meta: metaCalls
|
||||
? new Metadata(Buffer.from(metaCalls, 'base64'))
|
||||
: undefined,
|
||||
name: chain,
|
||||
ss58Format,
|
||||
tokenDecimals,
|
||||
tokenSymbol
|
||||
}
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
const UNKNOWN_CHAIN: Chain = {
|
||||
name: 'Any',
|
||||
ss58Format: 42,
|
||||
tokenDecimals: 0,
|
||||
tokenSymbol: 'UNIT'
|
||||
};
|
||||
|
||||
export default function findChain (genesisHash?: string | null): Chain {
|
||||
return chains.get(genesisHash || '') || UNKNOWN_CHAIN;
|
||||
}
|
||||
@ -1,3 +1,9 @@
|
||||
// Copyright 2019 @polkadot/extension-chains authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
// Generated via (in api) `yarn chain:info --ws wss://kusama-rpc.polkadot.io`
|
||||
|
||||
export default {
|
||||
chain: 'Kusama CC1',
|
||||
genesisHash: '0x3fd7b9eb6a00376e5be61f01abb429ffb0b104be05eaff4d458da48fcd425baf',
|
||||
14
packages/extension-chains/src/types.ts
Normal file
14
packages/extension-chains/src/types.ts
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2019 @polkadot/extension-chains 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 { Metadata } from '@polkadot/types';
|
||||
|
||||
export interface Chain {
|
||||
name: string;
|
||||
genesisHash?: string;
|
||||
meta?: Metadata;
|
||||
ss58Format: number;
|
||||
tokenDecimals: number;
|
||||
tokenSymbol: string;
|
||||
}
|
||||
@ -7,6 +7,7 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@polkadot/react-identicon": "^0.43.0-beta.11",
|
||||
"@polkadot/react-qr": "^0.43.0-beta.10",
|
||||
"@types/react-router": "^5.0.3",
|
||||
"@types/react-router-dom": "^4.3.5",
|
||||
"react-router": "^5.0.1",
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountJson } from '@polkadot/extension/background/types';
|
||||
|
||||
import React, { useContext, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@ -9,7 +11,7 @@ import { ActionBar, ActionContext, Address, Link } from '../../components';
|
||||
import { editAccount } from '../../messaging';
|
||||
import { Name } from '../../partials';
|
||||
|
||||
interface Props {
|
||||
interface Props extends AccountJson {
|
||||
address: string;
|
||||
className?: string;
|
||||
}
|
||||
@ -19,22 +21,22 @@ function Account ({ address, className }: Props): React.ReactElement<Props> {
|
||||
const [isEditing, setEditing] = useState(false);
|
||||
const [editedName, setName] = useState<string | null>(null);
|
||||
|
||||
const toggleEdit = (): void =>
|
||||
setEditing(!isEditing);
|
||||
const saveChanges = (): void => {
|
||||
const _toggleEdit = (): void => setEditing(!isEditing);
|
||||
const _saveChanges = (): void => {
|
||||
if (editedName && editedName !== name) {
|
||||
editAccount(address, editedName)
|
||||
.then((): void => onAction())
|
||||
.catch((error: Error) => console.error(error));
|
||||
}
|
||||
|
||||
toggleEdit();
|
||||
_toggleEdit();
|
||||
};
|
||||
|
||||
return (
|
||||
<Address
|
||||
address={address}
|
||||
className={className}
|
||||
name={editedName}
|
||||
>
|
||||
{isEditing && (
|
||||
<Name
|
||||
@ -42,12 +44,12 @@ function Account ({ address, className }: Props): React.ReactElement<Props> {
|
||||
className='edit-name'
|
||||
isFocussed
|
||||
label={null}
|
||||
onBlur={saveChanges}
|
||||
onBlur={_saveChanges}
|
||||
onChange={setName}
|
||||
/>
|
||||
)}
|
||||
<ActionBar>
|
||||
<Link onClick={toggleEdit}>Edit</Link>
|
||||
<Link onClick={_toggleEdit}>Edit</Link>
|
||||
<Link to={`/account/forget/${address}`}>Forget</Link>
|
||||
</ActionBar>
|
||||
</Address>
|
||||
@ -56,7 +58,7 @@ function Account ({ address, className }: Props): React.ReactElement<Props> {
|
||||
|
||||
export default styled(Account)`
|
||||
.edit-name {
|
||||
left: 4.75rem;
|
||||
left: 3rem;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: -0.5rem;
|
||||
|
||||
@ -4,11 +4,14 @@
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { AccountContext, Button, Header, Link, Tip } from '../../components';
|
||||
import { AccountContext, Button, Header, Link, MediaContext, Tip } from '../../components';
|
||||
import Account from './Account';
|
||||
|
||||
export default function Accounts (): React.ReactElement<{}> {
|
||||
type Props = {};
|
||||
|
||||
export default function Accounts (): React.ReactElement<Props> {
|
||||
const accounts = useContext(AccountContext);
|
||||
const mediaAllowed = useContext(MediaContext);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -19,10 +22,10 @@ export default function Accounts (): React.ReactElement<{}> {
|
||||
{
|
||||
(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 }): React.ReactNode => (
|
||||
: accounts.map((json, index): React.ReactNode => (
|
||||
<Account
|
||||
address={address}
|
||||
key={address}
|
||||
{...json}
|
||||
key={`${index}:${json.address}`}
|
||||
/>
|
||||
))
|
||||
}
|
||||
@ -32,8 +35,14 @@ export default function Accounts (): React.ReactElement<{}> {
|
||||
/>
|
||||
<Button
|
||||
label='I have a pre-existing seed, import the account'
|
||||
to='/account/import'
|
||||
to='/account/import-seed'
|
||||
/>
|
||||
{mediaAllowed && (
|
||||
<Button
|
||||
label='I have an external account, add it via QR'
|
||||
to='/account/import-qr'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -20,11 +20,11 @@ interface Props {
|
||||
|
||||
function Request ({ authId, className, isFirst, request: { origin }, url }: Props): React.ReactElement<Props> {
|
||||
const onAction = useContext(ActionContext);
|
||||
const onApprove = (): Promise<void> =>
|
||||
const _onApprove = (): Promise<void> =>
|
||||
approveAuthRequest(authId)
|
||||
.then((): void => onAction())
|
||||
.catch((error: Error) => console.error(error));
|
||||
const onReject = (): Promise<void> =>
|
||||
const _onReject = (): Promise<void> =>
|
||||
rejectAuthRequest(authId)
|
||||
.then((): void => onAction())
|
||||
.catch((error: Error) => console.error(error));
|
||||
@ -35,7 +35,7 @@ function Request ({ authId, className, isFirst, request: { origin }, url }: Prop
|
||||
icon={
|
||||
<Icon
|
||||
icon='X'
|
||||
onClick={onReject}
|
||||
onClick={_onReject}
|
||||
/>
|
||||
}
|
||||
intro={
|
||||
@ -43,14 +43,14 @@ function Request ({ authId, className, isFirst, request: { origin }, url }: Prop
|
||||
}
|
||||
>
|
||||
<ActionBar>
|
||||
<Link isDanger onClick={onReject}>Reject</Link>
|
||||
<Link isDanger onClick={_onReject}>Reject</Link>
|
||||
</ActionBar>
|
||||
{isFirst && (
|
||||
<>
|
||||
<Tip header='access' type='warn'>Only approve this request if you trust the application. Approving gives the application access to the addresses of your accounts.</Tip>
|
||||
<Button
|
||||
label='Yes, allow this application access'
|
||||
onClick={onApprove}
|
||||
onClick={_onApprove}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -23,7 +23,7 @@ export default function Create (): React.ReactElement<Props> {
|
||||
}, []);
|
||||
|
||||
// FIXME Duplicated between here and Import.tsx
|
||||
const onCreate = (): void => {
|
||||
const _onCreate = (): void => {
|
||||
// this should always be the case
|
||||
if (name && password && account) {
|
||||
createAccountSuri(name, password, account.seed)
|
||||
@ -55,7 +55,7 @@ export default function Create (): React.ReactElement<Props> {
|
||||
>
|
||||
<Button
|
||||
label='Add the account with the generated seed'
|
||||
onClick={onCreate}
|
||||
onClick={_onCreate}
|
||||
/>
|
||||
</Address>
|
||||
)}
|
||||
|
||||
@ -13,7 +13,8 @@ type Props = RouteComponentProps<{ address: string }>;
|
||||
|
||||
function Forget ({ match: { params: { address } } }: Props): React.ReactElement<Props> {
|
||||
const onAction = useContext(ActionContext);
|
||||
const onClick = (): Promise<void> =>
|
||||
|
||||
const _onClick = (): Promise<void> =>
|
||||
forgetAccount(address)
|
||||
.then((): void => onAction('/'))
|
||||
.catch((error: Error) => console.error(error));
|
||||
@ -27,7 +28,7 @@ function Forget ({ match: { params: { address } } }: Props): React.ReactElement<
|
||||
<Button
|
||||
isDanger
|
||||
label='I want to forget this account'
|
||||
onClick={onClick}
|
||||
onClick={_onClick}
|
||||
/>
|
||||
</Address>
|
||||
</div>
|
||||
|
||||
54
packages/extension-ui/src/Popup/ImportQr.tsx
Normal file
54
packages/extension-ui/src/Popup/ImportQr.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, { useContext, useState } from 'react';
|
||||
import { QrScanAddress } from '@polkadot/react-qr';
|
||||
|
||||
import { ActionContext, Address, Button, Header } from '../components';
|
||||
import { createAccountExternal } from '../messaging';
|
||||
import { Back, Name } from '../partials';
|
||||
|
||||
type Props = {};
|
||||
|
||||
export default function ImportQr (): React.ReactElement<Props> {
|
||||
const onAction = useContext(ActionContext);
|
||||
const [account, setAccount] = useState<null | { address: string; genesisHash: string }>(null);
|
||||
const [name, setName] = useState<string | null>(null);
|
||||
|
||||
// FIXME Duplicated between here and Create.tsx
|
||||
const _onCreate = (): void => {
|
||||
if (account && name) {
|
||||
createAccountExternal(name, account.address, account.genesisHash)
|
||||
.then((): void => onAction('/'))
|
||||
.catch((error: Error) => console.error(error));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Header label='import external' />
|
||||
<Back />
|
||||
{!account && <QrScanAddress onScan={setAccount} />}
|
||||
{account && (
|
||||
<>
|
||||
<Name
|
||||
isFocussed
|
||||
onChange={setName}
|
||||
/>
|
||||
<Address
|
||||
{...account}
|
||||
name={name}
|
||||
>
|
||||
{name && (
|
||||
<Button
|
||||
label='Add the account with identified address'
|
||||
onClick={_onCreate}
|
||||
/>
|
||||
)}
|
||||
</Address>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -16,13 +16,13 @@ export default function Import (): React.ReactElement<Props> {
|
||||
const [name, setName] = useState<string | null>(null);
|
||||
const [password, setPassword] = useState<string | null>(null);
|
||||
|
||||
const onChangeSeed = (suri: string): Promise<void> =>
|
||||
const _onChangeSeed = (suri: string): Promise<void> =>
|
||||
validateSeed(suri)
|
||||
.then(setAccount)
|
||||
.catch((): void => setAccount(null));
|
||||
|
||||
// FIXME Duplicated between here and Create.tsx
|
||||
const onCreate = (): void => {
|
||||
const _onCreate = (): void => {
|
||||
// this should always be the case
|
||||
if (name && password && account) {
|
||||
createAccountSuri(name, password, account.suri)
|
||||
@ -39,7 +39,7 @@ export default function Import (): React.ReactElement<Props> {
|
||||
isError={!account}
|
||||
isFocussed
|
||||
label='existing 12 or 24-word mnemonic seed'
|
||||
onChange={onChangeSeed}
|
||||
onChange={_onChangeSeed}
|
||||
/>
|
||||
{account && <Name onChange={setName} />}
|
||||
{account && name && <Password onChange={setPassword} />}
|
||||
@ -50,7 +50,7 @@ export default function Import (): React.ReactElement<Props> {
|
||||
>
|
||||
<Button
|
||||
label='Add the account with the supplied seed'
|
||||
onClick={onCreate}
|
||||
onClick={_onCreate}
|
||||
/>
|
||||
</Address>
|
||||
)}
|
||||
@ -13,7 +13,7 @@ import { Back } from '../../partials';
|
||||
|
||||
const options = settings.availablePrefixes.map(({ text, value }): { text: string; value: string } => ({
|
||||
text: value === -1
|
||||
? 'Substrate (default)'
|
||||
? 'Default (Substrate or as specified)'
|
||||
: text,
|
||||
value: `${value}`
|
||||
}));
|
||||
|
||||
@ -3,12 +3,13 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { BlockNumber, ExtrinsicEra, ExtrinsicPayload } from '@polkadot/types/interfaces';
|
||||
import { SignerPayloadJSON } from '@polkadot/types/types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import fromMetadata from '@polkadot/api-metadata/extrinsics/fromMetadata';
|
||||
import findChain from '@polkadot/extension/chains';
|
||||
import { GenericCall, Metadata } from '@polkadot/types';
|
||||
import findChain from '@polkadot/extension-chains';
|
||||
import { createType, GenericCall, Metadata } from '@polkadot/types';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
interface MethodJson {
|
||||
@ -17,12 +18,10 @@ interface MethodJson {
|
||||
}
|
||||
|
||||
interface Props {
|
||||
blockNumber: BlockNumber;
|
||||
className?: string;
|
||||
genesisHash: string;
|
||||
isDecoded: boolean;
|
||||
method: string;
|
||||
payload: ExtrinsicPayload;
|
||||
request: SignerPayloadJSON;
|
||||
url: string;
|
||||
}
|
||||
|
||||
@ -75,7 +74,10 @@ function renderMortality (era: ExtrinsicEra, blockNumber: BlockNumber): string {
|
||||
return `mortal, valid from #${formatNumber(mortal.birth(blockNumber))} to #${formatNumber(mortal.death(blockNumber))}`;
|
||||
}
|
||||
|
||||
function Details ({ blockNumber, className, genesisHash, isDecoded, method, payload: { era, nonce, tip }, url }: Props): React.ReactElement<Props> {
|
||||
function Details ({ className, isDecoded, payload, request, url }: Props): React.ReactElement<Props> {
|
||||
const blockNumber = createType('BlockNumber', request.blockNumber);
|
||||
const { genesisHash, method } = request;
|
||||
const { era, nonce, tip } = payload;
|
||||
const chain = findChain(genesisHash);
|
||||
|
||||
return (
|
||||
@ -99,7 +101,7 @@ function Details ({ blockNumber, className, genesisHash, isDecoded, method, payl
|
||||
<td className='data'>{formatNumber(tip)}</td>
|
||||
</tr>
|
||||
)}
|
||||
{renderMethod(method, (chain && isDecoded) ? chain.meta : null)}
|
||||
{renderMethod(method, isDecoded ? chain.meta : null)}
|
||||
<tr>
|
||||
<td className='label'>lifetime</td>
|
||||
<td className='data'>{renderMortality(era, blockNumber)}</td>
|
||||
|
||||
54
packages/extension-ui/src/Popup/Signing/Qr.tsx
Normal file
54
packages/extension-ui/src/Popup/Signing/Qr.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 { ExtrinsicPayload } from '@polkadot/types/interfaces';
|
||||
import { SignerPayloadJSON } from '@polkadot/types/types';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { QrDisplayPayload, QrScanSignature } from '@polkadot/react-qr';
|
||||
|
||||
import { Button } from '../../components';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
onSignature: ({ signature }: { signature: string }) => void;
|
||||
payload: ExtrinsicPayload;
|
||||
request: SignerPayloadJSON;
|
||||
}
|
||||
|
||||
const CMD_MORTAL = 2;
|
||||
|
||||
function Qr ({ className, onSignature, payload, request }: Props): React.ReactElement<Props> {
|
||||
const [isScanning, setIsScanning] = useState(false);
|
||||
const [payloadU8a, setPayloadU8a] = useState(new Uint8Array());
|
||||
|
||||
useEffect((): void => setPayloadU8a(payload.toU8a()), [payload]);
|
||||
|
||||
const _onShowQr = (): void => setIsScanning(true);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{isScanning
|
||||
? <QrScanSignature onScan={onSignature} />
|
||||
: <QrDisplayPayload
|
||||
address={request.address}
|
||||
cmd={CMD_MORTAL}
|
||||
payload={payloadU8a}
|
||||
/>
|
||||
}
|
||||
{!isScanning && (
|
||||
<Button
|
||||
label='Scan signature via camera'
|
||||
onClick={_onShowQr}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default styled(Qr)`
|
||||
padding: 0.75rem 1.5rem 0 1.5rem;
|
||||
`;
|
||||
@ -2,49 +2,70 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { RequestExtrinsicSign } from '@polkadot/extension/background/types';
|
||||
import { ExtrinsicPayload } from '@polkadot/types/interfaces';
|
||||
import { AccountJson, RequestExtrinsicSign } from '@polkadot/extension/background/types';
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import React, { useContext, useState, useEffect } from 'react';
|
||||
import { createType } from '@polkadot/types';
|
||||
|
||||
import { ActionBar, ActionContext, Address, Link } from '../../components';
|
||||
import { approveSignPassword, cancelSignRequest } from '../../messaging';
|
||||
import { approveSignPassword, approveSignSignature, cancelSignRequest } from '../../messaging';
|
||||
import Details from './Details';
|
||||
import Qr from './Qr';
|
||||
import Unlock from './Unlock';
|
||||
|
||||
interface Props {
|
||||
account: AccountJson;
|
||||
isFirst: boolean;
|
||||
request: RequestExtrinsicSign;
|
||||
signId: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export default function Request ({ isFirst, request, signId, url }: Props): React.ReactElement<Props> {
|
||||
export default function Request ({ account: { isExternal }, isFirst, request, signId, url }: Props): React.ReactElement<Props> | null {
|
||||
const onAction = useContext(ActionContext);
|
||||
const onCancel = (): Promise<void> =>
|
||||
const [payload, setPayload] = useState<ExtrinsicPayload | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
setPayload(createType('ExtrinsicPayload', request, { version: request.version }));
|
||||
}, [request]);
|
||||
|
||||
if (!payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const _onCancel = (): Promise<void> =>
|
||||
cancelSignRequest(signId)
|
||||
.then((): void => onAction())
|
||||
.catch((error: Error) => console.error(error));
|
||||
const onSign = (password: string): Promise<void> =>
|
||||
const _onSign = (password: string): Promise<void> =>
|
||||
approveSignPassword(signId, password)
|
||||
.then((): void => onAction());
|
||||
const blockNumber = createType('BlockNumber', request.blockNumber);
|
||||
const payload = createType('ExtrinsicPayload', request, { version: request.version });
|
||||
.then((): void => onAction())
|
||||
.catch((error: Error) => console.error(error));
|
||||
const _onSignature = ({ signature }: { signature: string }): Promise<void> =>
|
||||
approveSignSignature(signId, signature)
|
||||
.then((): void => onAction())
|
||||
.catch((error: Error) => console.error(error));
|
||||
|
||||
return (
|
||||
<Address address={request.address}>
|
||||
<Details
|
||||
blockNumber={blockNumber}
|
||||
genesisHash={request.genesisHash}
|
||||
isDecoded={isFirst}
|
||||
method={request.method}
|
||||
payload={payload}
|
||||
url={url}
|
||||
/>
|
||||
{isExternal && isFirst
|
||||
? <Qr
|
||||
payload={payload}
|
||||
request={request}
|
||||
onSignature={_onSignature}
|
||||
/>
|
||||
: <Details
|
||||
isDecoded={isFirst}
|
||||
payload={payload}
|
||||
request={request}
|
||||
url={url}
|
||||
/>
|
||||
}
|
||||
{isFirst && !isExternal && <Unlock onSign={_onSign} />}
|
||||
<ActionBar>
|
||||
<Link isDanger onClick={onCancel}>Cancel</Link>
|
||||
<Link isDanger onClick={_onCancel}>Cancel</Link>
|
||||
</ActionBar>
|
||||
{isFirst && <Unlock onSign={onSign} />}
|
||||
</Address>
|
||||
);
|
||||
}
|
||||
|
||||
@ -15,16 +15,16 @@ export default function Unlock ({ className, onSign }: Props): React.ReactElemen
|
||||
const [error, setError] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const onClick = (): Promise<void> =>
|
||||
onSign(password)
|
||||
.catch((error): void => setError(error.message));
|
||||
|
||||
useEffect((): void => {
|
||||
if (error) {
|
||||
setError('');
|
||||
}
|
||||
}, [password]);
|
||||
|
||||
const _onClick = (): Promise<void> =>
|
||||
onSign(password)
|
||||
.catch((error): void => setError(error.message));
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Input
|
||||
@ -36,7 +36,7 @@ export default function Unlock ({ className, onSign }: Props): React.ReactElemen
|
||||
/>
|
||||
<Button
|
||||
label='Sign the transaction'
|
||||
onClick={onClick}
|
||||
onClick={_onClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -13,8 +13,9 @@ export default function Signing (): React.ReactElement<{}> {
|
||||
return (
|
||||
<div>
|
||||
<Header label='transactions' />
|
||||
{requests.map(({ id, request, url }, index): React.ReactNode => (
|
||||
{requests.map(({ account, id, request, url }, index): React.ReactNode => (
|
||||
<Request
|
||||
account={account}
|
||||
isFirst={index === 0}
|
||||
key={id}
|
||||
request={request}
|
||||
|
||||
@ -10,7 +10,8 @@ type Props = {};
|
||||
|
||||
export default function Welcome (): React.ReactElement<Props> {
|
||||
const onAction = useContext(ActionContext);
|
||||
const onClick = (): void => {
|
||||
|
||||
const _onClick = (): void => {
|
||||
window.localStorage.setItem('welcome_read', 'ok');
|
||||
onAction();
|
||||
};
|
||||
@ -28,7 +29,7 @@ export default function Welcome (): React.ReactElement<Props> {
|
||||
... we are not in the information collection business (even anonymized).
|
||||
<Button
|
||||
label='Understood, let me continue'
|
||||
onClick={onClick}
|
||||
onClick={_onClick}
|
||||
/>
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
@ -11,17 +11,31 @@ import settings from '@polkadot/ui-settings';
|
||||
import { setAddressPrefix } from '@polkadot/util-crypto';
|
||||
|
||||
import { Loading } from '../components';
|
||||
import { AccountContext, ActionContext, AuthorizeContext, SigningContext } from '../components/contexts';
|
||||
import { AccountContext, ActionContext, AuthorizeContext, MediaContext, SigningContext } from '../components/contexts';
|
||||
import { subscribeAccounts, subscribeAuthorize, subscribeSigning } from '../messaging';
|
||||
import Accounts from './Accounts';
|
||||
import Authorize from './Authorize';
|
||||
import Create from './Create';
|
||||
import Forget from './Forget';
|
||||
import Import from './Import';
|
||||
import ImportQr from './ImportQr';
|
||||
import ImportSeed from './ImportSeed';
|
||||
import Settings from './Settings';
|
||||
import Signing from './Signing';
|
||||
import Welcome from './Welcome';
|
||||
|
||||
// Request permission for video, based on access we can hide/show import
|
||||
async function requestMediaAccess (): Promise<boolean> {
|
||||
try {
|
||||
await navigator.mediaDevices.getUserMedia({ video: true });
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Permission for video declined', error.message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// load the ui settings, actually only used for address prefix atm
|
||||
// probably overkill (however can replace once we have actual others)
|
||||
const { prefix } = settings.get();
|
||||
@ -32,10 +46,11 @@ setAddressPrefix((prefix === -1 ? 42 : prefix) as Prefix);
|
||||
export default function Popup (): React.ReactElement<{}> {
|
||||
const [accounts, setAccounts] = useState<null | AccountJson[]>(null);
|
||||
const [authRequests, setAuthRequests] = useState<null | AuthorizeRequest[]>(null);
|
||||
const [mediaAllowed, setMediaAllowed] = useState(false);
|
||||
const [signRequests, setSignRequests] = useState<null | SigningRequest[]>(null);
|
||||
const [isWelcomeDone, setWelcomeDone] = useState(false);
|
||||
|
||||
const onAction = (to?: string): void => {
|
||||
const _onAction = (to?: string): void => {
|
||||
setWelcomeDone(window.localStorage.getItem('welcome_read') === 'ok');
|
||||
|
||||
if (to) {
|
||||
@ -45,11 +60,12 @@ export default function Popup (): React.ReactElement<{}> {
|
||||
|
||||
useEffect((): void => {
|
||||
Promise.all([
|
||||
requestMediaAccess().then(setMediaAllowed),
|
||||
subscribeAccounts(setAccounts),
|
||||
subscribeAuthorize(setAuthRequests),
|
||||
subscribeSigning(setSignRequests)
|
||||
]).catch((error: Error) => console.error(error));
|
||||
onAction();
|
||||
_onAction();
|
||||
}, []);
|
||||
|
||||
const Root = isWelcomeDone
|
||||
@ -62,18 +78,21 @@ export default function Popup (): React.ReactElement<{}> {
|
||||
|
||||
return (
|
||||
<Loading>{accounts && authRequests && signRequests && (
|
||||
<ActionContext.Provider value={onAction}>
|
||||
<ActionContext.Provider value={_onAction}>
|
||||
<AccountContext.Provider value={accounts}>
|
||||
<AuthorizeContext.Provider value={authRequests}>
|
||||
<SigningContext.Provider value={signRequests}>
|
||||
<Switch>
|
||||
<Route path='/account/create' component={Create} />
|
||||
<Route path='/account/forget/:address' component={Forget} />
|
||||
<Route path='/account/import' component={Import} />
|
||||
<Route path='/settings' component={Settings} />
|
||||
<Route exact path='/' component={Root} />
|
||||
</Switch>
|
||||
</SigningContext.Provider>
|
||||
<MediaContext.Provider value={mediaAllowed}>
|
||||
<SigningContext.Provider value={signRequests}>
|
||||
<Switch>
|
||||
<Route path='/account/create' component={Create} />
|
||||
<Route path='/account/forget/:address' component={Forget} />
|
||||
<Route path='/account/import-qr' component={ImportQr} />
|
||||
<Route path='/account/import-seed' component={ImportSeed} />
|
||||
<Route path='/settings' component={Settings} />
|
||||
<Route exact path='/' component={Root} />
|
||||
</Switch>
|
||||
</SigningContext.Provider>
|
||||
</MediaContext.Provider>
|
||||
</AuthorizeContext.Provider>
|
||||
</AccountContext.Provider>
|
||||
</ActionContext.Provider>
|
||||
|
||||
@ -3,10 +3,12 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountJson } from '@polkadot/extension/background/types';
|
||||
import { Chain } from '@polkadot/extension-chains/types';
|
||||
import { Prefix } from '@polkadot/util-crypto/address/types';
|
||||
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import findChain from '@polkadot/extension-chains';
|
||||
import Identicon from '@polkadot/react-identicon';
|
||||
import settings from '@polkadot/ui-settings';
|
||||
import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';
|
||||
@ -19,12 +21,40 @@ interface Props {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
name?: React.ReactNode | null;
|
||||
genesisHash?: string | null;
|
||||
theme?: 'polkadot' | 'substrate';
|
||||
}
|
||||
|
||||
function Address ({ address, children, className, name, theme = 'polkadot' }: Props): React.ReactElement<Props> {
|
||||
// find an account in our list
|
||||
function findAccount (accounts: AccountJson[], publicKey: Uint8Array): AccountJson | null {
|
||||
const pkStr = publicKey.toString();
|
||||
|
||||
return accounts.find(({ address }): boolean =>
|
||||
decodeAddress(address).toString() === pkStr
|
||||
) || null;
|
||||
}
|
||||
|
||||
// recodes an supplied address using the prefix/genesisHash, include the actual saved account & chain
|
||||
function recodeAddress (address: string, accounts: AccountJson[], genesisHash?: string | null): [string, AccountJson | null, Chain] {
|
||||
// decode and create a shortcut for the encoded address
|
||||
const publicKey = decodeAddress(address);
|
||||
|
||||
// find our account using the actual publicKey, and then find the associated chain
|
||||
const account = findAccount(accounts, publicKey);
|
||||
const chain = findChain((account && account.genesisHash) || genesisHash);
|
||||
|
||||
return [
|
||||
// always allow the actual settings to override the display
|
||||
encodeAddress(publicKey, (settings.prefix === -1 ? chain.ss58Format : settings.prefix) as Prefix),
|
||||
account,
|
||||
chain
|
||||
];
|
||||
}
|
||||
|
||||
function Address ({ address, children, className, genesisHash, name, theme = 'polkadot' }: Props): React.ReactElement<Props> {
|
||||
const accounts = useContext(AccountContext);
|
||||
const [account, setAccount] = useState<AccountJson | null>(null);
|
||||
const [chain, setChain] = useState<Chain | null>(null);
|
||||
const [formatted, setFormatted] = useState<string | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
@ -32,21 +62,16 @@ function Address ({ address, children, className, name, theme = 'polkadot' }: Pr
|
||||
return;
|
||||
}
|
||||
|
||||
const addrU8a = decodeAddress(address);
|
||||
const addrU8aStr = addrU8a.toString();
|
||||
const [formatted, account, chain] = recodeAddress(address, accounts, genesisHash);
|
||||
|
||||
setFormatted(
|
||||
encodeAddress(addrU8a, (settings.prefix === -1 ? 42 : settings.prefix) as Prefix)
|
||||
);
|
||||
setAccount(
|
||||
accounts.find((account): boolean =>
|
||||
decodeAddress(account.address).toString() === addrU8aStr
|
||||
) || null
|
||||
);
|
||||
setFormatted(formatted);
|
||||
setChain(chain);
|
||||
setAccount(account);
|
||||
}, [address]);
|
||||
|
||||
return (
|
||||
<IconBox
|
||||
banner={chain && chain.genesisHash && chain.name}
|
||||
className={className}
|
||||
icon={
|
||||
<Identicon
|
||||
|
||||
@ -8,14 +8,16 @@ import styled from 'styled-components';
|
||||
import defaults from './defaults';
|
||||
|
||||
interface Props {
|
||||
banner?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function Box ({ children, className }: Props): React.ReactElement<Props> {
|
||||
function Box ({ banner, children, className }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<article className={className}>
|
||||
{children}
|
||||
{banner && <div className='banner'>{banner}</div>}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@ -30,4 +32,16 @@ export default styled(Box)`
|
||||
font-size: ${defaults.fontSize};
|
||||
margin: ${defaults.boxMargin};
|
||||
padding: 0.75rem 1rem;
|
||||
position: relative;
|
||||
|
||||
.banner {
|
||||
background: darkorange;
|
||||
border-radius: 0 ${defaults.borderRadius} 0 ${defaults.borderRadius};
|
||||
color: white;
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
@ -9,6 +9,7 @@ import Box from './Box';
|
||||
import defaults from './defaults';
|
||||
|
||||
interface Props {
|
||||
banner?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
icon: React.ReactNode;
|
||||
@ -17,10 +18,13 @@ interface Props {
|
||||
theme?: 'polkadot' | 'substrate';
|
||||
}
|
||||
|
||||
function IconBox ({ children, className, icon, intro }: Props): React.ReactElement<Props> {
|
||||
function IconBox ({ banner, children, className, icon, intro }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<div className={className}>
|
||||
<Box className='details'>
|
||||
<Box
|
||||
banner={banner}
|
||||
className='details'
|
||||
>
|
||||
<div className='intro'>{intro}</div>
|
||||
<div className='children'>{children}</div>
|
||||
</Box>
|
||||
|
||||
@ -12,11 +12,13 @@ const noop = (to?: string): void => {};
|
||||
const AccountContext = React.createContext<AccountJson[]>([]);
|
||||
const ActionContext = React.createContext<(to?: string) => void>(noop);
|
||||
const AuthorizeContext = React.createContext<AuthorizeRequest[]>([]);
|
||||
const MediaContext = React.createContext<boolean>(false);
|
||||
const SigningContext = React.createContext<SigningRequest[]>([]);
|
||||
|
||||
export {
|
||||
AccountContext,
|
||||
ActionContext,
|
||||
AuthorizeContext,
|
||||
MediaContext,
|
||||
SigningContext
|
||||
};
|
||||
|
||||
@ -66,14 +66,6 @@ export async function forgetAccount (address: string): Promise<boolean> {
|
||||
return sendMessage('pri(accounts.forget)', { address });
|
||||
}
|
||||
|
||||
export async function getAccounts (): Promise<AccountJson[]> {
|
||||
return sendMessage('pri(accounts.list)');
|
||||
}
|
||||
|
||||
export async function getAuthRequests (): Promise<AuthorizeRequest[]> {
|
||||
return sendMessage('pri(authorize.requests)');
|
||||
}
|
||||
|
||||
export async function rejectAuthRequest (id: string): Promise<boolean> {
|
||||
return sendMessage('pri(authorize.reject)', { id });
|
||||
}
|
||||
@ -82,10 +74,6 @@ export async function approveAuthRequest (id: string): Promise<boolean> {
|
||||
return sendMessage('pri(authorize.approve)', { id });
|
||||
}
|
||||
|
||||
export async function getSignRequests (): Promise<SigningRequest[]> {
|
||||
return sendMessage('pri(signing.requests)');
|
||||
}
|
||||
|
||||
export async function cancelSignRequest (id: string): Promise<boolean> {
|
||||
return sendMessage('pri(signing.cancel)', { id });
|
||||
}
|
||||
@ -94,6 +82,14 @@ export async function approveSignPassword (id: string, password: string): Promis
|
||||
return sendMessage('pri(signing.approve.password)', { id, password });
|
||||
}
|
||||
|
||||
export async function approveSignSignature (id: string, signature: string): Promise<boolean> {
|
||||
return sendMessage('pri(signing.approve.signature)', { id, signature });
|
||||
}
|
||||
|
||||
export async function createAccountExternal (name: string, address: string, genesisHash: string): Promise<boolean> {
|
||||
return sendMessage('pri(accounts.create.external)', { address, genesisHash, name });
|
||||
}
|
||||
|
||||
export async function createAccountSuri (name: string, password: string, suri: string, type?: KeypairType): Promise<boolean> {
|
||||
return sendMessage('pri(accounts.create.suri)', { name, password, suri, type });
|
||||
}
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
<meta charset="utf-8">
|
||||
<title>polkadot{.js}</title>
|
||||
</head>
|
||||
<body style="background:#f5f6f7; box-sizing:border-box; height:580px; margin:0; overflow-x: hidden; padding:0 1rem; width:480px;">
|
||||
<div id="root"></div>
|
||||
<body style="background:#f5f6f7; height:580px; margin:0; overflow-x:hidden; text-align:center">
|
||||
<div id="root" style="box-sizing:border-box; margin:0 auto; padding:0 1rem; text-align:left; width:480px"></div>
|
||||
<script src='./popup.js'></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||
import { AccountJson, AuthorizeRequest, RequestAccountCreateSuri, RequestAccountEdit, RequestAuthorizeApprove, RequestAuthorizeReject, RequestSigningApprovePassword, RequestSigningCancel, RequestSeedCreate, ResponseSeedCreate, RequestSeedValidate, ResponseSeedValidate, RequestAccountForget, SigningRequest, RequestTypes, ResponseTypes, MessageTypes } from '../types';
|
||||
import { AccountJson, AuthorizeRequest, RequestAccountCreateExternal, RequestAccountCreateSuri, RequestAccountEdit, RequestAuthorizeApprove, RequestAuthorizeReject, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSeedCreate, ResponseSeedCreate, RequestSeedValidate, ResponseSeedValidate, RequestAccountForget, SigningRequest, RequestTypes, ResponseTypes, MessageTypes } from '../types';
|
||||
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
|
||||
@ -18,7 +18,10 @@ const SEED_DEFAULT_LENGTH = 12;
|
||||
const SEED_LENGTHS = [12, 24];
|
||||
|
||||
function transformAccounts (accounts: SubjectInfo): AccountJson[] {
|
||||
return Object.values(accounts).map(({ json }): AccountJson => json);
|
||||
return Object.values(accounts).map(({ json: { address, meta } }): AccountJson => ({
|
||||
address,
|
||||
...meta
|
||||
}));
|
||||
}
|
||||
|
||||
export default class Extension {
|
||||
@ -28,6 +31,12 @@ export default class Extension {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
private accountsCreateExternal ({ address, genesisHash, name }: RequestAccountCreateExternal): boolean {
|
||||
keyring.addExternal(address, { name, genesisHash });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private accountsCreateSuri ({ genesisHash, name, password, suri, type }: RequestAccountCreateSuri): boolean {
|
||||
keyring.addUri(suri, password, { genesisHash, name }, type);
|
||||
|
||||
@ -50,10 +59,6 @@ export default class Extension {
|
||||
return true;
|
||||
}
|
||||
|
||||
private accountsList (): AccountJson[] {
|
||||
return transformAccounts(accountsObservable.subject.getValue());
|
||||
}
|
||||
|
||||
// FIXME This looks very much like what we have in Tabs
|
||||
private accountsSubscribe (id: string, port: chrome.runtime.Port): boolean {
|
||||
const cb = createSubscription<'pri(accounts.subscribe)'>(id, port);
|
||||
@ -93,10 +98,6 @@ export default class Extension {
|
||||
return true;
|
||||
}
|
||||
|
||||
private authorizeRequests (): AuthorizeRequest[] {
|
||||
return this.state.allAuthRequests;
|
||||
}
|
||||
|
||||
// FIXME This looks very much like what we have in accounts
|
||||
private authorizeSubscribe (id: string, port: chrome.runtime.Port): boolean {
|
||||
const cb = createSubscription<'pri(authorize.subscribe)'>(id, port);
|
||||
@ -162,6 +163,18 @@ export default class Extension {
|
||||
return true;
|
||||
}
|
||||
|
||||
private signingApproveSignature ({ id, signature }: RequestSigningApproveSignature): boolean {
|
||||
const queued = this.state.getSignRequest(id);
|
||||
|
||||
assert(queued, 'Unable to find request');
|
||||
|
||||
const { resolve } = queued;
|
||||
|
||||
resolve({ id, signature });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private signingCancel ({ id }: RequestSigningCancel): boolean {
|
||||
const queued = this.state.getSignRequest(id);
|
||||
|
||||
@ -174,10 +187,6 @@ export default class Extension {
|
||||
return true;
|
||||
}
|
||||
|
||||
private signingRequests (): SigningRequest[] {
|
||||
return this.state.allSignRequests;
|
||||
}
|
||||
|
||||
// FIXME This looks very much like what we have in authorization
|
||||
private signingSubscribe (id: string, port: chrome.runtime.Port): boolean {
|
||||
const cb = createSubscription<'pri(signing.subscribe)'>(id, port);
|
||||
@ -203,12 +212,12 @@ export default class Extension {
|
||||
case 'pri(authorize.reject)':
|
||||
return this.authorizeReject(request as RequestAuthorizeApprove);
|
||||
|
||||
case 'pri(authorize.requests)':
|
||||
return this.authorizeRequests();
|
||||
|
||||
case 'pri(authorize.subscribe)':
|
||||
return this.authorizeSubscribe(id, port);
|
||||
|
||||
case 'pri(accounts.create.external)':
|
||||
return this.accountsCreateExternal(request as RequestAccountCreateExternal);
|
||||
|
||||
case 'pri(accounts.create.suri)':
|
||||
return this.accountsCreateSuri(request as RequestAccountCreateSuri);
|
||||
|
||||
@ -218,9 +227,6 @@ export default class Extension {
|
||||
case 'pri(accounts.edit)':
|
||||
return this.accountsEdit(request as RequestAccountEdit);
|
||||
|
||||
case 'pri(accounts.list)':
|
||||
return this.accountsList();
|
||||
|
||||
case 'pri(accounts.subscribe)':
|
||||
return this.accountsSubscribe(id, port);
|
||||
|
||||
@ -233,12 +239,12 @@ export default class Extension {
|
||||
case 'pri(signing.approve.password)':
|
||||
return this.signingApprovePassword(request as RequestSigningApprovePassword);
|
||||
|
||||
case 'pri(signing.approve.signature)':
|
||||
return this.signingApproveSignature(request as RequestSigningApproveSignature);
|
||||
|
||||
case 'pri(signing.cancel)':
|
||||
return this.signingCancel(request as RequestSigningCancel);
|
||||
|
||||
case 'pri(signing.requests)':
|
||||
return this.signingRequests();
|
||||
|
||||
case 'pri(signing.subscribe)':
|
||||
return this.signingSubscribe(id, port);
|
||||
|
||||
|
||||
@ -92,7 +92,7 @@ export default class State {
|
||||
extension.windows.create({
|
||||
// This is not allowed on FF, only on Chrome - disable completely
|
||||
// focused: true,
|
||||
height: 581,
|
||||
height: 621,
|
||||
left: 150,
|
||||
top: 150,
|
||||
type: 'popup',
|
||||
|
||||
@ -43,20 +43,19 @@ export interface SigningRequest {
|
||||
// [MessageType]: [RequestType, ResponseType, SubscriptionMessageType?]
|
||||
export interface RequestSignatures {
|
||||
// private/internal requests, i.e. from a popup
|
||||
'pri(accounts.create.external)': [RequestAccountCreateExternal, boolean];
|
||||
'pri(accounts.create.suri)': [RequestAccountCreateSuri, boolean];
|
||||
'pri(accounts.edit)': [RequestAccountEdit, boolean];
|
||||
'pri(accounts.forget)': [RequestAccountForget, boolean];
|
||||
'pri(accounts.list)': [RequestAccountList, AccountJson[]];
|
||||
'pri(accounts.subscribe)': [RequestAccountSubscribe, boolean, AccountJson[]];
|
||||
'pri(authorize.approve)': [RequestAuthorizeApprove, boolean];
|
||||
'pri(authorize.reject)': [RequestAuthorizeReject, boolean];
|
||||
'pri(authorize.requests)': [RequestAuthorizeRequests, AuthorizeRequest[]];
|
||||
'pri(authorize.subscribe)': [RequestAuthorizeSubscribe, boolean, AuthorizeRequest[]];
|
||||
'pri(seed.create)': [RequestSeedCreate, ResponseSeedCreate];
|
||||
'pri(seed.validate)': [RequestSeedValidate, ResponseSeedValidate];
|
||||
'pri(signing.approve.password)': [RequestSigningApprovePassword, boolean];
|
||||
'pri(signing.approve.signature)': [RequestSigningApproveSignature, boolean];
|
||||
'pri(signing.cancel)': [RequestSigningCancel, boolean];
|
||||
'pri(signing.requests)': [RequestSigningRequests, SigningRequest[]];
|
||||
'pri(signing.subscribe)': [RequestSigningSubscribe, boolean, SigningRequest[]];
|
||||
// public/external requests, i.e. from a page
|
||||
'pub(accounts.list)': [RequestAccountList, InjectedAccount[]];
|
||||
@ -94,10 +93,14 @@ export interface RequestAuthorizeReject {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type RequestAuthorizeRequests = null;
|
||||
|
||||
export type RequestAuthorizeSubscribe = null;
|
||||
|
||||
export interface RequestAccountCreateExternal {
|
||||
address: string;
|
||||
genesisHash?: string | null;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface RequestAccountCreateSuri {
|
||||
name: string;
|
||||
genesisHash?: string | null;
|
||||
@ -127,12 +130,15 @@ export interface RequestSigningApprovePassword {
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RequestSigningApproveSignature {
|
||||
id: string;
|
||||
signature: string;
|
||||
}
|
||||
|
||||
export interface RequestSigningCancel {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type RequestSigningRequests = null;
|
||||
|
||||
export type RequestSigningSubscribe = null;
|
||||
|
||||
export interface RequestSeedCreate {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,41 +0,0 @@
|
||||
// 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 { Metadata } from '@polkadot/types';
|
||||
|
||||
// imports chain details, generally metadata. For the generation of these,
|
||||
// inside the api, run `yarn chain:info [--ws <url>]`
|
||||
import alexander from './alexander';
|
||||
import devPolka051 from './dev-polkadot-051';
|
||||
import kusama from './kusama';
|
||||
|
||||
interface Chain {
|
||||
name: string;
|
||||
genesisHash: string;
|
||||
meta?: Metadata;
|
||||
ss58Format: number;
|
||||
tokenDecimals: number;
|
||||
tokenSymbol: string;
|
||||
}
|
||||
|
||||
type Chains = Record<string, Chain>;
|
||||
|
||||
const chains: Chains = [alexander, devPolka051, kusama].reduce((chains: Chains, { chain, genesisHash, metaCalls, ss58Format, tokenDecimals, tokenSymbol }): Chains => {
|
||||
chains[genesisHash] = {
|
||||
genesisHash,
|
||||
meta: metaCalls
|
||||
? new Metadata(Buffer.from(metaCalls, 'base64'))
|
||||
: undefined,
|
||||
name: chain,
|
||||
ss58Format,
|
||||
tokenDecimals,
|
||||
tokenSymbol
|
||||
};
|
||||
|
||||
return chains;
|
||||
}, {});
|
||||
|
||||
export default function findChain (genesisHash: string): Chain | null {
|
||||
return chains[genesisHash] || null;
|
||||
}
|
||||
@ -2,10 +2,11 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { ResponseTypes, TransportRequestMessage, TransportResponseMessage, MessageTypes, RequestTypes, MessageTypesWithNullRequest, SubscriptionMessageTypes, MessageTypesWithNoSubscriptions, MessageTypesWithSubscriptions } from '../background/types';
|
||||
|
||||
import { injectExtension } from '@polkadot/extension-inject';
|
||||
|
||||
import Injected from './Injected';
|
||||
import { ResponseTypes, TransportRequestMessage, TransportResponseMessage, MessageTypes, RequestTypes, MessageTypesWithNullRequest, SubscriptionMessageTypes, MessageTypesWithNoSubscriptions, MessageTypesWithSubscriptions } from '../background/types';
|
||||
|
||||
// when sending a message from the injector to the extension, we
|
||||
// - create an event - this we send to the loader
|
||||
|
||||
@ -14,6 +14,7 @@ const manifest = require('./manifest.json');
|
||||
|
||||
const packages = [
|
||||
'extension',
|
||||
'extension-chains',
|
||||
'extension-inject',
|
||||
'extension-ui'
|
||||
];
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
"paths": {
|
||||
"@polkadot/extension": ["packages/extension/src"],
|
||||
"@polkadot/extension/*": ["packages/extension/src/*"],
|
||||
"@polkadot/extension-chains": ["packages/extension-chains/src"],
|
||||
"@polkadot/extension-chains/*": ["packages/extension-chains/src/*"],
|
||||
"@polkadot/extension-dapp": ["packages/extension-dapp/src"],
|
||||
"@polkadot/extension-dapp/*": ["packages/extension-dapp/src/*"],
|
||||
"@polkadot/extension-inject": ["packages/extension-inject/src"],
|
||||
|
||||
56
yarn.lock
56
yarn.lock
@ -2061,6 +2061,16 @@
|
||||
react-copy-to-clipboard "^5.0.1"
|
||||
styled-components "^4.3.1"
|
||||
|
||||
"@polkadot/react-qr@^0.43.0-beta.10":
|
||||
version "0.43.0-beta.10"
|
||||
resolved "https://registry.yarnpkg.com/@polkadot/react-qr/-/react-qr-0.43.0-beta.10.tgz#ab050288473d04822a5b85e0eb6d184c224d0656"
|
||||
integrity sha512-2PQn3QRfv7CTT1Xl5JvLmZatk1IIFnaCXaLMWWY54M3JlrwVSAHn3OMKlmHw/kwS8bYoSDKbYL/Qhrtustalrw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
"@types/react-qr-reader" "^2.1.1"
|
||||
qrcode-generator "^1.4.3"
|
||||
react-qr-reader "^2.2.1"
|
||||
|
||||
"@polkadot/rpc-core@^0.91.0-beta.22":
|
||||
version "0.91.0-beta.22"
|
||||
resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-0.91.0-beta.22.tgz#886855d016eea3143f993d81ee3ed6b8b7a59899"
|
||||
@ -2406,6 +2416,13 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-qr-reader@^2.1.1":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-qr-reader/-/react-qr-reader-2.1.2.tgz#da7a674cf9e3ceb1c026e89d5aa41fb1587fc66b"
|
||||
integrity sha512-tDLqqiQMZlTKBKtWedcf7QZ3L9fyz8nPHNq/j+1mcJoEvrdM3Y89i3/FKwdmZ4H7G6uUk4JqShpWXLCuYpZK+w==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-router-dom@^4.3.5":
|
||||
version "4.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.5.tgz#72f229967690c890d00f96e6b85e9ee5780db31f"
|
||||
@ -8767,6 +8784,11 @@ jsprim@^1.2.2:
|
||||
json-schema "0.2.3"
|
||||
verror "1.10.0"
|
||||
|
||||
jsqr@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/jsqr/-/jsqr-1.2.0.tgz#f93fc65fa7d1ded78b1bcb020fa044352b04261a"
|
||||
integrity sha512-wKcQS9QC2VHGk7aphWCp1RrFyC0CM6fMgC5prZZ2KV/Lk6OKNoCod9IR6bao+yx3KPY0gZFC5dc+h+KFzCI0Wg==
|
||||
|
||||
jsx-ast-utils@^2.1.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz#4d4973ebf8b9d2837ee91a8208cc66f3a2776cfb"
|
||||
@ -11583,6 +11605,11 @@ q@^1.1.2, q@^1.5.1:
|
||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
|
||||
|
||||
qrcode-generator@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.3.tgz#4876e8f280e65b6c94615f4c19c484f6b964b199"
|
||||
integrity sha512-++rVRvMRq5BlHfmAafl8a4ppUntzUxCCUTT2t0siUgqKwdnqRzY8IH6f6WSX5dZUhD2Ul5/MIKuTJddflwrGzw==
|
||||
|
||||
qs@6.7.0:
|
||||
version "6.7.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||
@ -11741,6 +11768,15 @@ react-lifecycles-compat@^3.0.4:
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||
|
||||
react-qr-reader@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-qr-reader/-/react-qr-reader-2.2.1.tgz#dc89046d1c1a1da837a683dd970de5926817d55b"
|
||||
integrity sha512-EL5JEj53u2yAOgtpAKAVBzD/SiKWn0Bl7AZy6ZrSf1lub7xHwtaXe6XSx36Wbhl1VMGmvmrwYMRwO1aSCT2fwA==
|
||||
dependencies:
|
||||
jsqr "^1.2.0"
|
||||
prop-types "^15.7.2"
|
||||
webrtc-adapter "^7.2.1"
|
||||
|
||||
react-router-dom@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.0.1.tgz#ee66f4a5d18b6089c361958e443489d6bab714be"
|
||||
@ -12363,6 +12399,13 @@ rsvp@^4.8.4:
|
||||
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
||||
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
|
||||
|
||||
rtcpeerconnection-shim@^1.2.15:
|
||||
version "1.2.15"
|
||||
resolved "https://registry.yarnpkg.com/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz#e7cc189a81b435324c4949aa3dfb51888684b243"
|
||||
integrity sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==
|
||||
dependencies:
|
||||
sdp "^2.6.0"
|
||||
|
||||
run-async@^2.2.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
|
||||
@ -12464,6 +12507,11 @@ schema-utils@^2.0.0, schema-utils@^2.0.1:
|
||||
ajv "^6.10.2"
|
||||
ajv-keywords "^3.4.1"
|
||||
|
||||
sdp@^2.10.0, sdp@^2.6.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/sdp/-/sdp-2.10.0.tgz#643fff1a43cdba54a739c7b202f56bd854474af2"
|
||||
integrity sha512-H+VjfyQpRz9GezhshJmkXTtCAT9/2g9az3GFDPYfGOz0eAOQU1fCrL3S9Dq/eUT9FtOyLi/czdR9PzK3fKUYOQ==
|
||||
|
||||
secp256k1@^3.7.0:
|
||||
version "3.7.1"
|
||||
resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.7.1.tgz#12e473e0e9a7c2f2d4d4818e722ad0e14cc1e2f1"
|
||||
@ -14454,6 +14502,14 @@ webpackbar@3.2.0:
|
||||
text-table "^0.2.0"
|
||||
wrap-ansi "^5.1.0"
|
||||
|
||||
webrtc-adapter@^7.2.1:
|
||||
version "7.3.0"
|
||||
resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-7.3.0.tgz#a65e18aad42759bab6ced7f8cfff11b051321e2a"
|
||||
integrity sha512-pKcwt6IR6RLCD6jlcdOOi88iVwdzppHlkOhtgTSuZHtYTxdD09t5fA1Di7GJU7je8oHcCBlNfb7zwBsetERnmQ==
|
||||
dependencies:
|
||||
rtcpeerconnection-shim "^1.2.15"
|
||||
sdp "^2.10.0"
|
||||
|
||||
websocket-driver@>=0.5.1:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user