Provide metadata from external source (#288)
* Provide metadata from external source * Listing via metadata interface * Updates * UI update * Storage & end-to-end
This commit is contained in:
parent
a2c0c8a433
commit
c9f4c2e4a6
@ -10,7 +10,7 @@ function copy_folder () {
|
||||
cp -r $SRC $DST
|
||||
}
|
||||
|
||||
yarn polkadot-dev-build-ts
|
||||
yarn build
|
||||
|
||||
copy_folder "extension-dapp"
|
||||
copy_folder "extension-inject"
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
"postinstall": "polkadot-dev-yarn-only",
|
||||
"start": "yarn watch",
|
||||
"test": "polkadot-dev-run-test",
|
||||
"watch": "cd packages/extension && webpack --config webpack.config.js --watch"
|
||||
"watch": "cd packages/extension && yarn polkadot-exec-webpack --config webpack.config.js --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.9.0",
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { MetadataDef } from '@polkadot/extension-inject/types';
|
||||
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||
import { AccountJson, AuthorizeRequest, MessageTypes, RequestAccountCreateExternal, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAuthorizeApprove, RequestAuthorizeReject, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSeedCreate, RequestTypes, ResponseAccountExport, RequestAccountForget, ResponseSeedCreate, RequestSeedValidate, ResponseSeedValidate, ResponseTypes, SigningRequest } from '../types';
|
||||
import { AccountJson, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountCreateExternal, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAuthorizeApprove, RequestAuthorizeReject, RequestMetadataApprove, RequestMetadataReject, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSeedCreate, RequestTypes, ResponseAccountExport, RequestAccountForget, ResponseSeedCreate, RequestSeedValidate, ResponseSeedValidate, ResponseTypes, SigningRequest } from '../types';
|
||||
|
||||
import extension from 'extensionizer';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
@ -108,7 +109,7 @@ export default class Extension {
|
||||
|
||||
// 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);
|
||||
const cb = createSubscription<'pri(authorize.requests)'>(id, port);
|
||||
const subscription = this.#state.authSubject.subscribe((requests: AuthorizeRequest[]): void =>
|
||||
cb(requests)
|
||||
);
|
||||
@ -121,6 +122,50 @@ export default class Extension {
|
||||
return true;
|
||||
}
|
||||
|
||||
private metadataApprove ({ id }: RequestMetadataApprove): boolean {
|
||||
const queued = this.#state.getMetaRequest(id);
|
||||
|
||||
assert(queued, 'Unable to find request');
|
||||
|
||||
const { resolve, request } = queued;
|
||||
|
||||
this.#state.saveMetadata(request);
|
||||
|
||||
resolve(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private metadataList (): MetadataDef[] {
|
||||
return this.#state.knownMetadata;
|
||||
}
|
||||
|
||||
private metadataReject ({ id }: RequestMetadataReject): boolean {
|
||||
const queued = this.#state.getMetaRequest(id);
|
||||
|
||||
assert(queued, 'Unable to find request');
|
||||
|
||||
const { reject } = queued;
|
||||
|
||||
reject(new Error('Rejected'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private metadataSubscribe (id: string, port: chrome.runtime.Port): boolean {
|
||||
const cb = createSubscription<'pri(metadata.requests)'>(id, port);
|
||||
const subscription = this.#state.metaSubject.subscribe((requests: MetadataRequest[]): void =>
|
||||
cb(requests)
|
||||
);
|
||||
|
||||
port.onDisconnect.addListener((): void => {
|
||||
unsubscribe(id);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private seedCreate ({ length = SEED_DEFAULT_LENGTH, type }: RequestSeedCreate): ResponseSeedCreate {
|
||||
const seed = mnemonicGenerate(length);
|
||||
|
||||
@ -199,7 +244,7 @@ export default class Extension {
|
||||
|
||||
// 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);
|
||||
const cb = createSubscription<'pri(signing.requests)'>(id, port);
|
||||
const subscription = this.#state.signSubject.subscribe((requests: SigningRequest[]): void =>
|
||||
cb(requests)
|
||||
);
|
||||
@ -228,9 +273,9 @@ export default class Extension {
|
||||
return this.authorizeApprove(request as RequestAuthorizeApprove);
|
||||
|
||||
case 'pri(authorize.reject)':
|
||||
return this.authorizeReject(request as RequestAuthorizeApprove);
|
||||
return this.authorizeReject(request as RequestAuthorizeReject);
|
||||
|
||||
case 'pri(authorize.subscribe)':
|
||||
case 'pri(authorize.requests)':
|
||||
return this.authorizeSubscribe(id, port);
|
||||
|
||||
case 'pri(accounts.create.external)':
|
||||
@ -251,6 +296,18 @@ export default class Extension {
|
||||
case 'pri(accounts.subscribe)':
|
||||
return this.accountsSubscribe(id, port);
|
||||
|
||||
case 'pri(metadata.approve)':
|
||||
return this.metadataApprove(request as RequestMetadataApprove);
|
||||
|
||||
case 'pri(metadata.list)':
|
||||
return this.metadataList();
|
||||
|
||||
case 'pri(metadata.reject)':
|
||||
return this.metadataReject(request as RequestMetadataReject);
|
||||
|
||||
case 'pri(metadata.requests)':
|
||||
return this.metadataSubscribe(id, port);
|
||||
|
||||
case 'pri(seed.create)':
|
||||
return this.seedCreate(request as RequestSeedCreate);
|
||||
|
||||
@ -266,7 +323,7 @@ export default class Extension {
|
||||
case 'pri(signing.cancel)':
|
||||
return this.signingCancel(request as RequestSigningCancel);
|
||||
|
||||
case 'pri(signing.subscribe)':
|
||||
case 'pri(signing.requests)':
|
||||
return this.signingSubscribe(id, port);
|
||||
|
||||
case 'pri(window.open)':
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
// 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 { MetadataDef } from '@polkadot/extension-inject/types';
|
||||
|
||||
import extension from 'extensionizer';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type StoreValue = Record<string, any>;
|
||||
|
||||
const lastError = (type: string): void => {
|
||||
const error = extension.runtime.lastError;
|
||||
|
||||
if (error) {
|
||||
console.error(`ChainStore.${type}:: runtime.lastError:`, error);
|
||||
}
|
||||
};
|
||||
|
||||
export default class Store {
|
||||
public all (cb: (key: string, value: MetadataDef) => void): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
extension.storage.local.get(null, (result: StoreValue): void => {
|
||||
lastError('all');
|
||||
|
||||
Object.entries(result).forEach(([key, value]): void => {
|
||||
cb(key, value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public get (key: string, cb: (value: MetadataDef) => void): void {
|
||||
extension.storage.local.get([key], (result: StoreValue): void => {
|
||||
lastError('get');
|
||||
|
||||
cb(result[key]);
|
||||
});
|
||||
}
|
||||
|
||||
public remove (key: string, cb?: () => void): void {
|
||||
extension.storage.local.remove(key, (): void => {
|
||||
lastError('remove');
|
||||
|
||||
cb && cb();
|
||||
});
|
||||
}
|
||||
|
||||
public set (key: string, value: MetadataDef, cb?: () => void): void {
|
||||
extension.storage.local.set({ [key]: value }, (): void => {
|
||||
lastError('set');
|
||||
|
||||
cb && cb();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -2,15 +2,17 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { ProviderMeta } from '@polkadot/extension-inject/types';
|
||||
import { knownMetadata, addMetadata } from '@polkadot/extension-chains';
|
||||
import { ProviderMeta, MetadataDef } from '@polkadot/extension-inject/types';
|
||||
import { JsonRpcResponse, ProviderInterface, ProviderInterfaceCallback } from '@polkadot/rpc-provider/types';
|
||||
|
||||
import { AccountJson, AuthorizeRequest, RequestAuthorizeTab, RequestRpcSend, RequestRpcSubscribe, RequestSign, ResponseRpcListProviders, ResponseSigning, SigningRequest, RequestRpcUnsubscribe } from '../types';
|
||||
import { AccountJson, AuthorizeRequest, MetadataRequest, RequestAuthorizeTab, RequestRpcSend, RequestRpcSubscribe, RequestSign, ResponseRpcListProviders, ResponseSigning, SigningRequest, RequestRpcUnsubscribe } from '../types';
|
||||
|
||||
import extension from 'extensionizer';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { assert } from '@polkadot/util';
|
||||
|
||||
import MetadataStore from './MetadataStore';
|
||||
|
||||
interface AuthRequest {
|
||||
id: string;
|
||||
idStr: string;
|
||||
@ -28,6 +30,14 @@ type AuthUrls = Record<string, {
|
||||
url: string;
|
||||
}>;
|
||||
|
||||
interface MetaRequest {
|
||||
id: string;
|
||||
request: MetadataDef;
|
||||
resolve: (result: boolean) => void;
|
||||
reject: (error: Error) => void;
|
||||
url: string;
|
||||
}
|
||||
|
||||
// List of providers passed into constructor. This is the list of providers
|
||||
// exposed by the extension.
|
||||
type Providers = Record<string, {
|
||||
@ -68,9 +78,13 @@ export default class State {
|
||||
|
||||
readonly #authRequests: Record<string, AuthRequest> = {};
|
||||
|
||||
readonly #metaStore = new MetadataStore();
|
||||
|
||||
// Map of providers currently injected in tabs
|
||||
readonly #injectedProviders: Map<chrome.runtime.Port, ProviderInterface> = new Map();
|
||||
|
||||
readonly #metaRequests: Record<string, MetaRequest> = {};
|
||||
|
||||
// Map of all providers exposed by the extension, they are retrievable by key
|
||||
readonly #providers: Providers;
|
||||
|
||||
@ -78,26 +92,34 @@ export default class State {
|
||||
|
||||
#windows: number[] = [];
|
||||
|
||||
public readonly authSubject: BehaviorSubject<AuthorizeRequest[]> = new BehaviorSubject([] as AuthorizeRequest[]);
|
||||
public readonly authSubject: BehaviorSubject<AuthorizeRequest[]> = new BehaviorSubject<AuthorizeRequest[]>([]);
|
||||
|
||||
public readonly signSubject: BehaviorSubject<SigningRequest[]> = new BehaviorSubject([] as SigningRequest[]);
|
||||
public readonly metaSubject: BehaviorSubject<MetadataRequest[]> = new BehaviorSubject<MetadataRequest[]>([]);
|
||||
|
||||
public readonly signSubject: BehaviorSubject<SigningRequest[]> = new BehaviorSubject<SigningRequest[]>([]);
|
||||
|
||||
constructor (providers: Providers = {}) {
|
||||
this.#providers = providers;
|
||||
|
||||
this.#metaStore.all((key: string, def: MetadataDef): void => {
|
||||
if (key.startsWith('metadata:')) {
|
||||
addMetadata(def);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public get hasAuthRequests (): boolean {
|
||||
return this.numAuthRequests === 0;
|
||||
}
|
||||
|
||||
public get hasSignRequests (): boolean {
|
||||
return this.numSignRequests === 0;
|
||||
public get knownMetadata (): MetadataDef[] {
|
||||
return knownMetadata();
|
||||
}
|
||||
|
||||
public get numAuthRequests (): number {
|
||||
return Object.keys(this.#authRequests).length;
|
||||
}
|
||||
|
||||
public get numMetaRequests (): number {
|
||||
return Object.keys(this.#metaRequests).length;
|
||||
}
|
||||
|
||||
public get numSignRequests (): number {
|
||||
return Object.keys(this.#signRequests).length;
|
||||
}
|
||||
@ -108,6 +130,12 @@ export default class State {
|
||||
.map(({ id, request, url }): AuthorizeRequest => ({ id, request, url }));
|
||||
}
|
||||
|
||||
public get allMetaRequests (): MetadataRequest[] {
|
||||
return Object
|
||||
.values(this.#metaRequests)
|
||||
.map(({ id, request, url }): MetadataRequest => ({ id, request, url }));
|
||||
}
|
||||
|
||||
public get allSignRequests (): SigningRequest[] {
|
||||
return Object
|
||||
.values(this.#signRequests)
|
||||
@ -149,6 +177,15 @@ export default class State {
|
||||
};
|
||||
}
|
||||
|
||||
private metaComplete = (id: string, fn: Function): (result: boolean | Error) => void => {
|
||||
return (result: boolean | Error): void => {
|
||||
delete this.#metaRequests[id];
|
||||
this.updateIconMeta(true);
|
||||
|
||||
fn(result);
|
||||
};
|
||||
}
|
||||
|
||||
private signComplete = (id: string, fn: Function): (result: ResponseSigning | Error) => void => {
|
||||
return (result: ResponseSigning | Error): void => {
|
||||
delete this.#signRequests[id];
|
||||
@ -168,11 +205,14 @@ export default class State {
|
||||
|
||||
private updateIcon (shouldClose?: boolean): void {
|
||||
const authCount = this.numAuthRequests;
|
||||
const metaCount = this.numMetaRequests;
|
||||
const signCount = this.numSignRequests;
|
||||
const text = (
|
||||
authCount
|
||||
? 'Auth'
|
||||
: (signCount ? `${signCount}` : '')
|
||||
: metaCount
|
||||
? 'Meta'
|
||||
: (signCount ? `${signCount}` : '')
|
||||
);
|
||||
|
||||
extension.browserAction.setBadgeText({ text });
|
||||
@ -187,6 +227,11 @@ export default class State {
|
||||
this.updateIcon(shouldClose);
|
||||
}
|
||||
|
||||
private updateIconMeta (shouldClose?: boolean): void {
|
||||
this.metaSubject.next(this.allMetaRequests);
|
||||
this.updateIcon(shouldClose);
|
||||
}
|
||||
|
||||
private updateIconSign (shouldClose?: boolean): void {
|
||||
this.signSubject.next(this.allSignRequests);
|
||||
this.updateIcon(shouldClose);
|
||||
@ -198,7 +243,7 @@ export default class State {
|
||||
if (this.#authUrls[idStr]) {
|
||||
assert(this.#authUrls[idStr].isAllowed, `The source ${url} is not allowed to interact with this extension`);
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject): void => {
|
||||
@ -227,10 +272,31 @@ export default class State {
|
||||
return true;
|
||||
}
|
||||
|
||||
public injectMetadata (url: string, request: MetadataDef): Promise<boolean> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
const id = getId();
|
||||
|
||||
this.#metaRequests[id] = {
|
||||
id,
|
||||
request,
|
||||
resolve: this.metaComplete(id, resolve),
|
||||
reject: this.metaComplete(id, reject),
|
||||
url
|
||||
};
|
||||
|
||||
this.updateIconMeta();
|
||||
this.popupOpen();
|
||||
});
|
||||
}
|
||||
|
||||
public getAuthRequest (id: string): AuthRequest {
|
||||
return this.#authRequests[id];
|
||||
}
|
||||
|
||||
public getMetaRequest (id: string): MetaRequest {
|
||||
return this.#metaRequests[id];
|
||||
}
|
||||
|
||||
public getSignRequest (id: string): SignRequest {
|
||||
return this.#signRequests[id];
|
||||
}
|
||||
@ -303,6 +369,12 @@ export default class State {
|
||||
return provider.unsubscribe(request.type, request.method, request.subscriptionId);
|
||||
}
|
||||
|
||||
public saveMetadata (meta: MetadataDef): void {
|
||||
this.#metaStore.set(`metadata:${meta.genesisHash}`, meta);
|
||||
|
||||
addMetadata(meta);
|
||||
}
|
||||
|
||||
public sign (url: string, request: RequestSign, account: AccountJson): Promise<ResponseSigning> {
|
||||
const id = getId();
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { InjectedAccount, ProviderMeta } from '@polkadot/extension-inject/types';
|
||||
import { InjectedAccount, InjectedMetadataKnown, MetadataDef, ProviderMeta } from '@polkadot/extension-inject/types';
|
||||
import { KeyringPair } from '@polkadot/keyring/types';
|
||||
import { JsonRpcResponse } from '@polkadot/rpc-provider/types';
|
||||
import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
|
||||
@ -12,9 +12,9 @@ import { RequestAuthorizeTab, ResponseSigning, RequestTypes, ResponseTypes, Mess
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
|
||||
import { assert } from '@polkadot/util';
|
||||
|
||||
import RequestBytesSign from '../RequestBytesSign';
|
||||
import RequestExtrinsicSign from '../RequestExtrinsicSign';
|
||||
|
||||
import State from './State';
|
||||
import { createSubscription, unsubscribe } from './subscriptions';
|
||||
|
||||
@ -57,22 +57,38 @@ export default class Tabs {
|
||||
|
||||
private getSigningPair (address: string): KeyringPair {
|
||||
const pair = keyring.getPair(address);
|
||||
|
||||
assert(pair, 'Unable to find keypair');
|
||||
|
||||
return pair;
|
||||
}
|
||||
|
||||
private bytesSign (url: string, request: SignerPayloadRaw): Promise<ResponseSigning> {
|
||||
const address = request.address;
|
||||
const pair = this.getSigningPair(address);
|
||||
|
||||
return this.#state.sign(url, new RequestBytesSign(request), { address, ...pair.meta });
|
||||
}
|
||||
|
||||
private extrinsicSign (url: string, request: SignerPayloadJSON): Promise<ResponseSigning> {
|
||||
const address = request.address;
|
||||
const pair = this.getSigningPair(address);
|
||||
|
||||
return this.#state.sign(url, new RequestExtrinsicSign(request), { address, ...pair.meta });
|
||||
}
|
||||
|
||||
private metadataProvide (url: string, request: MetadataDef): Promise<boolean> {
|
||||
return this.#state.injectMetadata(url, request);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
private metadataList (url: string): InjectedMetadataKnown[] {
|
||||
return this.#state.knownMetadata.map(({ genesisHash, specVersion }) => ({
|
||||
genesisHash,
|
||||
specVersion
|
||||
}));
|
||||
}
|
||||
|
||||
private rpcListProviders (): Promise<ResponseRpcListProviders> {
|
||||
return this.#state.rpcListProviders();
|
||||
}
|
||||
@ -135,6 +151,12 @@ export default class Tabs {
|
||||
case 'pub(extrinsic.sign)':
|
||||
return this.extrinsicSign(url, request as SignerPayloadJSON);
|
||||
|
||||
case 'pub(metadata.list)':
|
||||
return this.metadataList(url);
|
||||
|
||||
case 'pub(metadata.provide)':
|
||||
return this.metadataProvide(url, request as MetadataDef);
|
||||
|
||||
case 'pub(rpc.listProviders)':
|
||||
return this.rpcListProviders();
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { InjectedAccount, ProviderList, ProviderMeta } from '@polkadot/extension-inject/types';
|
||||
import { InjectedAccount, MetadataDef, ProviderList, ProviderMeta, InjectedMetadataKnown } from '@polkadot/extension-inject/types';
|
||||
import { JsonRpcResponse } from '@polkadot/rpc-provider/types';
|
||||
import { KeypairType } from '@polkadot/util-crypto/types';
|
||||
import { KeyringPair } from '@polkadot/keyring/types';
|
||||
@ -36,6 +36,12 @@ export interface AuthorizeRequest {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface MetadataRequest {
|
||||
id: string;
|
||||
request: MetadataDef;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface SigningRequest {
|
||||
account: AccountJson;
|
||||
id: string;
|
||||
@ -54,13 +60,17 @@ export interface RequestSignatures {
|
||||
'pri(accounts.subscribe)': [RequestAccountSubscribe, boolean, AccountJson[]];
|
||||
'pri(authorize.approve)': [RequestAuthorizeApprove, boolean];
|
||||
'pri(authorize.reject)': [RequestAuthorizeReject, boolean];
|
||||
'pri(authorize.subscribe)': [RequestAuthorizeSubscribe, boolean, AuthorizeRequest[]];
|
||||
'pri(authorize.requests)': [RequestAuthorizeSubscribe, boolean, AuthorizeRequest[]];
|
||||
'pri(metadata.approve)': [RequestMetadataApprove, boolean];
|
||||
'pri(metadata.reject)': [RequestMetadataReject, boolean];
|
||||
'pri(metadata.requests)': [RequestMetadataSubscribe, boolean, MetadataRequest[]];
|
||||
'pri(metadata.list)': [null, MetadataDef[]];
|
||||
'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.subscribe)': [RequestSigningSubscribe, boolean, SigningRequest[]];
|
||||
'pri(signing.requests)': [RequestSigningSubscribe, boolean, SigningRequest[]];
|
||||
'pri(window.open)': [null, boolean];
|
||||
// public/external requests, i.e. from a page
|
||||
'pub(accounts.list)': [RequestAccountList, InjectedAccount[]];
|
||||
@ -68,6 +78,8 @@ export interface RequestSignatures {
|
||||
'pub(authorize.tab)': [RequestAuthorizeTab, null];
|
||||
'pub(bytes.sign)': [SignerPayloadRaw, ResponseSigning];
|
||||
'pub(extrinsic.sign)': [SignerPayloadJSON, ResponseSigning];
|
||||
'pub(metadata.list)': [null, InjectedMetadataKnown[]];
|
||||
'pub(metadata.provide)': [MetadataDef, boolean];
|
||||
'pub(rpc.listProviders)': [void, ResponseRpcListProviders];
|
||||
'pub(rpc.send)': [RequestRpcSend, JsonRpcResponse];
|
||||
'pub(rpc.startProvider)': [string, ProviderMeta];
|
||||
@ -107,6 +119,16 @@ export interface RequestAuthorizeReject {
|
||||
|
||||
export type RequestAuthorizeSubscribe = null;
|
||||
|
||||
export interface RequestMetadataApprove {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface RequestMetadataReject {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type RequestMetadataSubscribe = null;
|
||||
|
||||
export interface RequestAccountCreateExternal {
|
||||
address: string;
|
||||
genesisHash?: string | null;
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
import { InjectedAccounts, InjectedAccount, Unsubcall } from '@polkadot/extension-inject/types';
|
||||
import { SendRequest } from './types';
|
||||
|
||||
// External to class, this.# is not private enough (yet)
|
||||
let sendRequest: SendRequest;
|
||||
|
||||
export default class Accounts implements InjectedAccounts {
|
||||
|
||||
@ -7,17 +7,21 @@ import { Injected } from '@polkadot/extension-inject/types';
|
||||
import { SendRequest } from './types';
|
||||
|
||||
import Accounts from './Accounts';
|
||||
import Metadata from './Metadata';
|
||||
import Signer from './Signer';
|
||||
|
||||
export default class implements Injected {
|
||||
public readonly accounts: Accounts;
|
||||
|
||||
public readonly metadata: Metadata;
|
||||
|
||||
public readonly provider: PostMessageProvider;
|
||||
|
||||
public readonly signer: Signer;
|
||||
|
||||
constructor (sendRequest: SendRequest) {
|
||||
this.accounts = new Accounts(sendRequest);
|
||||
this.metadata = new Metadata(sendRequest);
|
||||
this.provider = new PostMessageProvider(sendRequest);
|
||||
this.signer = new Signer(sendRequest);
|
||||
}
|
||||
|
||||
23
packages/extension-base/src/page/Metadata.ts
Normal file
23
packages/extension-base/src/page/Metadata.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2019-2020 @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 { InjectedMetadata, InjectedMetadataKnown, MetadataDef } from '@polkadot/extension-inject/types';
|
||||
import { SendRequest } from './types';
|
||||
|
||||
// External to class, this.# is not private enough (yet)
|
||||
let sendRequest: SendRequest;
|
||||
|
||||
export default class Metadata implements InjectedMetadata {
|
||||
constructor (_sendRequest: SendRequest) {
|
||||
sendRequest = _sendRequest;
|
||||
}
|
||||
|
||||
public get (): Promise<InjectedMetadataKnown[]> {
|
||||
return sendRequest('pub(metadata.list)');
|
||||
}
|
||||
|
||||
public provide (definition: MetadataDef): Promise<boolean> {
|
||||
return sendRequest('pub(metadata.provide)', definition);
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,9 @@ interface SubscriptionHandler {
|
||||
type: string;
|
||||
}
|
||||
|
||||
// External to class, this.# is not private enough (yet)
|
||||
let sendRequest: SendRequest;
|
||||
|
||||
/**
|
||||
* @name PostMessageProvider
|
||||
*
|
||||
@ -32,8 +35,6 @@ export default class PostMessageProvider implements InjectedProvider {
|
||||
// Whether or not the actual extension background provider is connected
|
||||
#isConnected = false
|
||||
|
||||
readonly #sendRequest: SendRequest;
|
||||
|
||||
// Subscription IDs are (historically) not guaranteed to be globally unique;
|
||||
// only unique for a given subscription method; which is why we identify
|
||||
// the subscriptions based on subscription id + type
|
||||
@ -43,16 +44,17 @@ export default class PostMessageProvider implements InjectedProvider {
|
||||
* @param {function} sendRequest The function to be called to send requests to the node
|
||||
* @param {function} subscriptionNotificationHandler Channel for receiving subscription messages
|
||||
*/
|
||||
public constructor (sendRequest: SendRequest) {
|
||||
public constructor (_sendRequest: SendRequest) {
|
||||
this.#eventemitter = new EventEmitter();
|
||||
this.#sendRequest = sendRequest;
|
||||
|
||||
sendRequest = _sendRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a clone of the object
|
||||
*/
|
||||
public clone (): PostMessageProvider {
|
||||
return new PostMessageProvider(this.#sendRequest);
|
||||
return new PostMessageProvider(sendRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,7 +82,7 @@ export default class PostMessageProvider implements InjectedProvider {
|
||||
}
|
||||
|
||||
public listProviders (): Promise<ProviderList> {
|
||||
return this.#sendRequest('pub(rpc.listProviders)', undefined);
|
||||
return sendRequest('pub(rpc.listProviders)', undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,7 +104,7 @@ export default class PostMessageProvider implements InjectedProvider {
|
||||
if (subscription) {
|
||||
const { callback, type } = subscription;
|
||||
|
||||
return this.#sendRequest(
|
||||
return sendRequest(
|
||||
'pub(rpc.subscribe)',
|
||||
{ type, method, params },
|
||||
(res) => {
|
||||
@ -114,7 +116,7 @@ export default class PostMessageProvider implements InjectedProvider {
|
||||
return id;
|
||||
});
|
||||
} else {
|
||||
return this.#sendRequest('pub(rpc.send)', { method, params });
|
||||
return sendRequest('pub(rpc.send)', { method, params });
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,9 +128,9 @@ export default class PostMessageProvider implements InjectedProvider {
|
||||
this.#isConnected = false;
|
||||
this.#eventemitter.emit('disconnected');
|
||||
|
||||
const meta = await this.#sendRequest('pub(rpc.startProvider)', key);
|
||||
const meta = await sendRequest('pub(rpc.startProvider)', key);
|
||||
|
||||
this.#sendRequest('pub(rpc.subscribeConnected)', null, (connected) => {
|
||||
sendRequest('pub(rpc.subscribeConnected)', null, (connected) => {
|
||||
this.#isConnected = connected;
|
||||
|
||||
if (connected) {
|
||||
|
||||
@ -6,14 +6,12 @@ import { Signer as SignerInterface, SignerResult } from '@polkadot/api/types';
|
||||
import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
|
||||
import { SendRequest } from './types';
|
||||
|
||||
// External to class, this.# is not private enough (yet)
|
||||
let sendRequest: SendRequest;
|
||||
let nextId = 0;
|
||||
|
||||
export default class Signer implements SignerInterface {
|
||||
constructor (_sendRequest: SendRequest) {
|
||||
// NOTE We are storing the `sendRequest` as a global to this class, since we don't
|
||||
// have private members in JS, we err on the side of caution and hide it away so it
|
||||
// is only available internally to the class.
|
||||
sendRequest = _sendRequest;
|
||||
}
|
||||
|
||||
|
||||
@ -5,8 +5,7 @@
|
||||
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||
"license": "Apache-2",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"edgeware-node-types": "^1.2.0"
|
||||
"@babel/runtime": "^7.9.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@polkadot/api": "*"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -2,21 +2,21 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Chain, ChainDef } from './types';
|
||||
import { MetadataDef } from '@polkadot/extension-inject/types';
|
||||
import { Chain } from './types';
|
||||
|
||||
import { Metadata, TypeRegistry } from '@polkadot/types';
|
||||
|
||||
// imports chain details, generally metadata. For the generation of these,
|
||||
// inside the api, run `yarn chain:info --ws <url>`
|
||||
import edgeware from './edgeware';
|
||||
import kusama from './kusama';
|
||||
|
||||
const chains: Map<string, Chain> = new Map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
[edgeware, kusama].map((def) => defToChain(def))
|
||||
const definitions: Map<string, MetadataDef> = new Map(
|
||||
[kusama].map((def) => [def.genesisHash, def])
|
||||
);
|
||||
|
||||
function defToChain ({ chain, genesisHash, icon, metaCalls, specVersion, ss58Format, tokenDecimals, tokenSymbol, types }: ChainDef): [string, Chain] {
|
||||
function metadataExpand (definition: MetadataDef): Chain {
|
||||
const { chain, genesisHash, icon, metaCalls, specVersion, ss58Format, tokenDecimals, tokenSymbol, types } = definition;
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ -33,7 +33,8 @@ function defToChain ({ chain, genesisHash, icon, metaCalls, specVersion, ss58For
|
||||
tokenSymbol
|
||||
}));
|
||||
|
||||
return [genesisHash, {
|
||||
return {
|
||||
definition,
|
||||
genesisHash: isUnknown
|
||||
? undefined
|
||||
: genesisHash,
|
||||
@ -46,26 +47,21 @@ function defToChain ({ chain, genesisHash, icon, metaCalls, specVersion, ss58For
|
||||
ss58Format,
|
||||
tokenDecimals,
|
||||
tokenSymbol
|
||||
}];
|
||||
};
|
||||
}
|
||||
|
||||
const [, UNKNOWN_CHAIN] = defToChain({
|
||||
chain: 'Unknown chain',
|
||||
genesisHash: '0x',
|
||||
icon: 'polkadot',
|
||||
specVersion: 0,
|
||||
ss58Format: 42,
|
||||
tokenDecimals: 0,
|
||||
tokenSymbol: 'Unit',
|
||||
types: {}
|
||||
});
|
||||
export function findChain (definitions: MetadataDef[], genesisHash?: string | null): Chain | null {
|
||||
const def = definitions.find((def) => def.genesisHash === genesisHash);
|
||||
|
||||
export function addChainDef (def: ChainDef): void {
|
||||
const [genesisHash, chain] = defToChain(def);
|
||||
|
||||
chains.set(genesisHash, chain);
|
||||
return def
|
||||
? metadataExpand(def)
|
||||
: null;
|
||||
}
|
||||
|
||||
export default function findChain (genesisHash?: string | null): Chain {
|
||||
return chains.get(genesisHash || '') || UNKNOWN_CHAIN;
|
||||
export function addMetadata (def: MetadataDef): void {
|
||||
definitions.set(def.genesisHash, def);
|
||||
}
|
||||
|
||||
export function knownMetadata (): MetadataDef[] {
|
||||
return [...definitions.values()];
|
||||
}
|
||||
|
||||
@ -2,9 +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 { MetadataDef } from '@polkadot/extension-inject/types';
|
||||
import { Registry } from '@polkadot/types/types';
|
||||
|
||||
export interface Chain {
|
||||
definition: MetadataDef;
|
||||
genesisHash?: string;
|
||||
hasMetadata: boolean;
|
||||
icon: string;
|
||||
@ -16,15 +18,3 @@ export interface Chain {
|
||||
tokenDecimals: number;
|
||||
tokenSymbol: string;
|
||||
}
|
||||
|
||||
export interface ChainDef {
|
||||
chain: string;
|
||||
genesisHash: string;
|
||||
icon: string;
|
||||
specVersion: number;
|
||||
ss58Format: number;
|
||||
tokenDecimals: number;
|
||||
tokenSymbol: string;
|
||||
types: Record<string, object | string>;
|
||||
metaCalls?: string;
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ export interface InjectedAccounts {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
subscribe: (cb: (accounts: InjectedAccount[]) => any) => Unsubcall;
|
||||
}
|
||||
|
||||
export interface InjectedExtensionInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
@ -47,6 +48,28 @@ export interface ProviderMeta {
|
||||
transport: string;
|
||||
}
|
||||
|
||||
export interface MetadataDef {
|
||||
chain: string;
|
||||
genesisHash: string;
|
||||
icon: string;
|
||||
specVersion: number;
|
||||
ss58Format: number;
|
||||
tokenDecimals: number;
|
||||
tokenSymbol: string;
|
||||
types: Record<string, object | string>;
|
||||
metaCalls?: string;
|
||||
}
|
||||
|
||||
export interface InjectedMetadataKnown {
|
||||
genesisHash: string;
|
||||
specVersion: number;
|
||||
}
|
||||
|
||||
export interface InjectedMetadata {
|
||||
get: () => Promise<InjectedMetadataKnown[]>;
|
||||
provide: (definition: MetadataDef) => Promise<boolean>;
|
||||
}
|
||||
|
||||
export type ProviderList = Record<string, ProviderMeta>
|
||||
|
||||
export interface InjectedProvider extends ProviderInterface {
|
||||
@ -62,8 +85,9 @@ export interface InjectedProviderWithMeta {
|
||||
|
||||
export interface Injected {
|
||||
accounts: InjectedAccounts;
|
||||
signer: InjectedSigner;
|
||||
metadata?: InjectedMetadata;
|
||||
provider?: InjectedProvider;
|
||||
signer: InjectedSigner;
|
||||
}
|
||||
|
||||
export interface InjectedWindowProvider {
|
||||
|
||||
@ -6,7 +6,7 @@ import React from 'react';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { AuthorizeContext, themes, Header, Icon } from '@polkadot/extension-ui/components';
|
||||
import { AuthorizeReqContext, themes, Header, Icon } from '@polkadot/extension-ui/components';
|
||||
import Authorize from '.';
|
||||
import { configure, mount, ReactWrapper } from 'enzyme';
|
||||
import { AuthorizeRequest } from '@polkadot/extension-base/background/types';
|
||||
@ -16,11 +16,11 @@ configure({ adapter: new Adapter() });
|
||||
|
||||
describe('Authorize', () => {
|
||||
const mountAuthorize = (authorizeRequests: AuthorizeRequest[] = []): ReactWrapper => mount(
|
||||
<AuthorizeContext.Provider value={authorizeRequests}>
|
||||
<AuthorizeReqContext.Provider value={authorizeRequests}>
|
||||
<ThemeProvider theme={themes.dark}>
|
||||
<Authorize />
|
||||
</ThemeProvider>
|
||||
</AuthorizeContext.Provider>);
|
||||
</AuthorizeReqContext.Provider>);
|
||||
|
||||
it('render component', () => {
|
||||
const wrapper = mountAuthorize();
|
||||
|
||||
@ -38,8 +38,8 @@ function Request ({ authId, className, isFirst, request: { origin }, url }: Prop
|
||||
An application, self-identifying as <span className='tab-name'>{origin}</span> is requesting access from{' '}
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
<span className='tab-url'>{url}</span>
|
||||
</a>.
|
||||
|
||||
@ -4,17 +4,17 @@
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { AuthorizeContext, Header } from '../../components';
|
||||
import { AuthorizeReqContext, Header } from '../../components';
|
||||
import Request from './Request';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export default function Authorize (): React.ReactElement<{}> {
|
||||
const requests = useContext(AuthorizeContext);
|
||||
const requests = useContext(AuthorizeReqContext);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Scroll isLastRequest={requests.length === 1}>
|
||||
<Header text='Authorize'/>
|
||||
<Header text='Authorize' />
|
||||
{requests.map(({ id, request, url }, index): React.ReactNode => (
|
||||
<Request
|
||||
authId={id}
|
||||
|
||||
125
packages/extension-ui/src/Popup/Metadata/Request.tsx
Normal file
125
packages/extension-ui/src/Popup/Metadata/Request.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
// Copyright 2019-2020 @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 { MetadataDef } from '@polkadot/extension-inject/types';
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ActionBar, ActionContext, Button, Link, Table, Warning } from '../../components';
|
||||
import useMetadata from '../../hooks/useMetadata';
|
||||
import { approveMetaRequest, rejectMetaRequest } from '../../messaging';
|
||||
interface Props {
|
||||
className?: string;
|
||||
request: MetadataDef;
|
||||
metaId: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
function Request ({ className, metaId, request, url }: Props): React.ReactElement<Props> {
|
||||
const chain = useMetadata(request.genesisHash);
|
||||
const onAction = useContext(ActionContext);
|
||||
const _onApprove = (): Promise<void> =>
|
||||
approveMetaRequest(metaId)
|
||||
.then((): void => onAction())
|
||||
.catch((error: Error) => console.error(error));
|
||||
const _onReject = (): Promise<void> =>
|
||||
rejectMetaRequest(metaId)
|
||||
.then((): void => onAction())
|
||||
.catch((error: Error) => console.error(error));
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Table>
|
||||
<tr>
|
||||
<td className='label'>from</td>
|
||||
<td className='data'>{url}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='label'>chain</td>
|
||||
<td className='data'>{request.chain}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='label'>icon</td>
|
||||
<td className='data'>{request.icon}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='label'>decimals</td>
|
||||
<td className='data'>{request.tokenDecimals}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='label'>symbol</td>
|
||||
<td className='data'>{request.tokenSymbol}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='label'>upgrade</td>
|
||||
<td className='data'>{chain ? chain.specVersion : 'unknown'} -> {request.specVersion}</td>
|
||||
</tr>
|
||||
</Table>
|
||||
<RequestInfo>
|
||||
<RequestWarning>This approval will add the metadata to your extension instance, allowing future requests to be decoded using this metadata.</RequestWarning>
|
||||
<AcceptButton onClick={_onApprove}>Yes, do this metadata update</AcceptButton>
|
||||
<RejectButton>
|
||||
<Link isDanger onClick={_onReject}>Reject</Link>
|
||||
</RejectButton>
|
||||
</RequestInfo>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const RequestInfo = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
background: ${({ theme }): string => theme.highlightedAreaBackground};
|
||||
`;
|
||||
|
||||
const AcceptButton = styled(Button)`
|
||||
width: 90%;
|
||||
margin: 25px auto 0;
|
||||
`;
|
||||
|
||||
const RequestWarning = styled(Warning)`
|
||||
margin: 24px 24px 0 1.45rem;
|
||||
`;
|
||||
|
||||
AcceptButton.displayName = 'AcceptButton';
|
||||
|
||||
const RejectButton = styled(ActionBar)`
|
||||
margin: 8px 0 15px 0;
|
||||
text-decoration: underline;
|
||||
`;
|
||||
|
||||
export default styled(Request)`
|
||||
.icon {
|
||||
background: ${({ theme }): string => theme.buttonBackgroundDanger};
|
||||
color: white;
|
||||
min-width: 18px;
|
||||
width: 14px;
|
||||
height: 18px;
|
||||
font-size: 10px;
|
||||
line-height: 20px;
|
||||
margin: 16px 15px 0 1.35rem;
|
||||
font-weight: 800;
|
||||
padding-left: 0.5px;
|
||||
}
|
||||
|
||||
.tab-info {
|
||||
overflow: hidden;
|
||||
margin: 0.75rem 20px 0 0;
|
||||
}
|
||||
|
||||
.tab-name,
|
||||
.tab-url {
|
||||
color: ${({ theme }): string => theme.textColor};
|
||||
display: inline-block;
|
||||
max-width: 20rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
29
packages/extension-ui/src/Popup/Metadata/index.tsx
Normal file
29
packages/extension-ui/src/Popup/Metadata/index.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2019-2020 @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 } from 'react';
|
||||
|
||||
import { Header, Loading, MetadataReqContext } from '../../components';
|
||||
import Request from './Request';
|
||||
|
||||
export default function Metadata (): React.ReactElement<{}> {
|
||||
const requests = useContext(MetadataReqContext);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header text='Metadata' />
|
||||
{requests[0]
|
||||
? (
|
||||
<Request
|
||||
key={requests[0].id}
|
||||
metaId={requests[0].id}
|
||||
request={requests[0].request}
|
||||
url={requests[0].url}
|
||||
/>
|
||||
)
|
||||
: <Loading />
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -8,10 +8,11 @@ import { AnyJson, SignerPayloadJSON } from '@polkadot/types/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import findChain from '@polkadot/extension-chains';
|
||||
import { formatNumber, bnToBn } from '@polkadot/util';
|
||||
|
||||
import { Table } from '../../components';
|
||||
import useMetadata from '../../hooks/useMetadata';
|
||||
|
||||
interface Decoded {
|
||||
args: AnyJson | null;
|
||||
method: Call | null;
|
||||
@ -25,12 +26,12 @@ interface Props {
|
||||
url: string;
|
||||
}
|
||||
|
||||
function decodeMethod (data: string, isDecoded: boolean, chain: Chain, specVersion: BN): Decoded {
|
||||
function decodeMethod (data: string, isDecoded: boolean, chain: Chain | null, specVersion: BN): Decoded {
|
||||
let args: AnyJson | null = null;
|
||||
let method: Call | null = null;
|
||||
|
||||
try {
|
||||
if (isDecoded && chain.hasMetadata && specVersion.eqn(chain.specVersion)) {
|
||||
if (isDecoded && chain && chain.hasMetadata && specVersion.eqn(chain.specVersion)) {
|
||||
method = chain.registry.createType('Call', data);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
args = (method.toHuman() as any).args;
|
||||
@ -94,7 +95,7 @@ function mortalityAsString (era: ExtrinsicEra, hexBlockNumber: string): string {
|
||||
}
|
||||
|
||||
function Extrinsic ({ className, isDecoded, payload: { era, nonce, tip }, request: { blockNumber, genesisHash, method, specVersion: hexSpec }, url }: Props): React.ReactElement<Props> {
|
||||
const chain = useRef(findChain(genesisHash)).current;
|
||||
const chain = useMetadata(genesisHash);
|
||||
const specVersion = useRef(bnToBn(hexSpec)).current;
|
||||
const [decoded, setDecoded] = useState<Decoded>({ args: null, method: null });
|
||||
|
||||
@ -103,84 +104,36 @@ function Extrinsic ({ className, isDecoded, payload: { era, nonce, tip }, reques
|
||||
}, [method, isDecoded, chain, specVersion]);
|
||||
|
||||
return (
|
||||
<table className={className}>
|
||||
<tbody>
|
||||
<Table className={className} isFull>
|
||||
<tr>
|
||||
<td className='label'>from</td>
|
||||
<td className='data'>{url}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='label'>{chain ? 'chain' : 'genesis'}</td>
|
||||
<td className='data'>{chain ? chain.name : genesisHash}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='label'>version</td>
|
||||
<td className='data'>{specVersion.toNumber()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='label'>nonce</td>
|
||||
<td className='data'>{formatNumber(nonce)}</td>
|
||||
</tr>
|
||||
{!tip.isEmpty && (
|
||||
<tr>
|
||||
<td className='label'>from</td>
|
||||
<td className='data'>{url}</td>
|
||||
<td className='label'>tip</td>
|
||||
<td className='data'>{formatNumber(tip)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='label'>{chain.isUnknown ? 'genesis' : 'chain'}</td>
|
||||
<td className='data'>{chain.isUnknown ? genesisHash : chain.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='label'>version</td>
|
||||
<td className='data'>{specVersion.toNumber()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='label'>nonce</td>
|
||||
<td className='data'>{formatNumber(nonce)}</td>
|
||||
</tr>
|
||||
{!tip.isEmpty && (
|
||||
<tr>
|
||||
<td className='label'>tip</td>
|
||||
<td className='data'>{formatNumber(tip)}</td>
|
||||
</tr>
|
||||
)}
|
||||
{renderMethod(method, decoded)}
|
||||
<tr>
|
||||
<td className='label'>lifetime</td>
|
||||
<td className='data'>{mortalityAsString(era, blockNumber)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
{renderMethod(method, decoded)}
|
||||
<tr>
|
||||
<td className='label'>lifetime</td>
|
||||
<td className='data'>{mortalityAsString(era, blockNumber)}</td>
|
||||
</tr>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
export default styled(Extrinsic)`
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
display: block;
|
||||
border: 0;
|
||||
font-size: ${({ theme }): string => theme.labelFontSize};
|
||||
line-height: ${({ theme }): string => theme.labelLineHeight};
|
||||
|
||||
td.data {
|
||||
max-width: 0;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
|
||||
pre {
|
||||
font-family: inherit;
|
||||
font-size: 0.75rem;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
td.label {
|
||||
opacity: 0.5;
|
||||
padding: 0 0.5rem;
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
details {
|
||||
cursor: pointer;
|
||||
max-width: 24rem;
|
||||
|
||||
&[open] summary {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
summary {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
export default React.memo(Extrinsic);
|
||||
|
||||
@ -36,6 +36,7 @@ export default function Request ({ account: { isExternal }, request, signId, url
|
||||
|
||||
useEffect((): void => {
|
||||
const inner = request.inner;
|
||||
|
||||
if ((inner as SignerPayloadRaw).data) {
|
||||
setHexBytes((inner as SignerPayloadRaw).data);
|
||||
setExtrinsic(null);
|
||||
@ -45,10 +46,10 @@ export default function Request ({ account: { isExternal }, request, signId, url
|
||||
}
|
||||
}, [request]);
|
||||
|
||||
const _onCancel = (): Promise<void> => {
|
||||
return cancelSignRequest(signId).then((): void => onAction())
|
||||
const _onCancel = (): Promise<void> =>
|
||||
cancelSignRequest(signId)
|
||||
.then((): void => onAction())
|
||||
.catch((error: Error) => console.error(error));
|
||||
};
|
||||
const _onSign = (password: string): Promise<void> =>
|
||||
approveSignPassword(signId, password)
|
||||
.then((): void => onAction())
|
||||
@ -60,6 +61,7 @@ export default function Request ({ account: { isExternal }, request, signId, url
|
||||
|
||||
if (extrinsic !== null) {
|
||||
const payload = request.inner as SignerPayloadJSON;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Address
|
||||
@ -92,6 +94,7 @@ export default function Request ({ account: { isExternal }, request, signId, url
|
||||
);
|
||||
} else if (hexBytes !== null) {
|
||||
const payload = request.inner as SignerPayloadRaw;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Address address={payload.address} />
|
||||
@ -105,9 +108,9 @@ export default function Request ({ account: { isExternal }, request, signId, url
|
||||
</SignArea>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const SignArea = styled(ButtonArea)`
|
||||
|
||||
@ -10,15 +10,7 @@ import { act } from 'react-dom/test-utils';
|
||||
import * as messaging from '@polkadot/extension-ui/messaging';
|
||||
import { AccountJson, SigningRequest } from '@polkadot/extension-base/background/types';
|
||||
import { flushAllPromises } from '@polkadot/extension-ui/testHelpers';
|
||||
import {
|
||||
AccountContext,
|
||||
ActionContext,
|
||||
themes,
|
||||
SigningContext,
|
||||
Address,
|
||||
Input,
|
||||
Button
|
||||
} from '@polkadot/extension-ui/components';
|
||||
import { AccountContext, ActionContext, Address, Button, Input, SigningReqContext, themes } from '@polkadot/extension-ui/components';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import Signing from '.';
|
||||
import TransactionIndex from './TransactionIndex';
|
||||
@ -52,9 +44,9 @@ describe('Signing requests', () => {
|
||||
emitter.on('request', setRequests);
|
||||
|
||||
return (
|
||||
<SigningContext.Provider value={requests}>
|
||||
<SigningReqContext.Provider value={requests}>
|
||||
<Signing />
|
||||
</SigningContext.Provider>
|
||||
</SigningReqContext.Provider>
|
||||
);
|
||||
}
|
||||
const mountComponent = async (): Promise<void> => {
|
||||
|
||||
@ -2,16 +2,24 @@
|
||||
// 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, useEffect } from 'react';
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { Header, Loading, SigningContext } from '../../components';
|
||||
import { Header, Loading, SigningReqContext } from '../../components';
|
||||
import Request from './Request';
|
||||
import TransactionIndex from './TransactionIndex';
|
||||
import { SigningRequest } from '@polkadot/extension-base/background/types';
|
||||
|
||||
function isInnerTransaction (signingRequest: SigningRequest): boolean {
|
||||
return 'blockNumber' in signingRequest.request.inner;
|
||||
}
|
||||
|
||||
export default function Signing (): React.ReactElement<{}> {
|
||||
const requests = useContext(SigningContext);
|
||||
const requests = useContext(SigningReqContext);
|
||||
const [requestIndex, setRequestIndex] = useState(0);
|
||||
const isTransaction = useMemo(
|
||||
() => isInnerTransaction(requests[requestIndex]),
|
||||
[requests, requestIndex]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (requestIndex >= requests.length) {
|
||||
@ -19,13 +27,9 @@ export default function Signing (): React.ReactElement<{}> {
|
||||
}
|
||||
}, [requests]);
|
||||
|
||||
function isTransaction (signingRequest: SigningRequest): boolean {
|
||||
return 'blockNumber' in signingRequest.request.inner;
|
||||
}
|
||||
|
||||
return requests[requestIndex] ? (
|
||||
<>
|
||||
<Header text={isTransaction(requests[requestIndex]) ? 'Transaction' : 'Sign message'}>
|
||||
<Header text={isTransaction ? 'Transaction' : 'Sign message'}>
|
||||
{requests.length > 1 && (
|
||||
<TransactionIndex
|
||||
index={requestIndex}
|
||||
@ -41,7 +45,7 @@ export default function Signing (): React.ReactElement<{}> {
|
||||
request={requests[requestIndex].request}
|
||||
signId={requests[requestIndex].id}
|
||||
url={requests[requestIndex].url}
|
||||
buttonText={isTransaction(requests[requestIndex]) ? 'Sign the transaction' : 'Sign the message'}
|
||||
buttonText={isTransaction ? 'Sign the transaction' : 'Sign the message'}
|
||||
/>
|
||||
</>
|
||||
) : <Loading />;
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountJson, AuthorizeRequest, SigningRequest } from '@polkadot/extension-base/background/types';
|
||||
import { AccountJson, AuthorizeRequest, MetadataRequest, SigningRequest } from '@polkadot/extension-base/background/types';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Route, Switch } from 'react-router';
|
||||
@ -10,8 +10,8 @@ import settings from '@polkadot/ui-settings';
|
||||
import { setSS58Format } from '@polkadot/util-crypto';
|
||||
|
||||
import { Loading } from '../components';
|
||||
import { AccountContext, ActionContext, AuthorizeContext, MediaContext, SigningContext } from '../components/contexts';
|
||||
import { subscribeAccounts, subscribeAuthorize, subscribeSigning } from '../messaging';
|
||||
import { AccountContext, ActionContext, AuthorizeReqContext, MediaContext, MetadataReqContext, SigningReqContext } from '../components/contexts';
|
||||
import { subscribeAccounts, subscribeAuthorizeRequests, subscribeMetadataRequests, subscribeSigningRequests } from '../messaging';
|
||||
import Accounts from './Accounts';
|
||||
import Authorize from './Authorize';
|
||||
import CreateAccount from './CreateAccount';
|
||||
@ -19,6 +19,7 @@ import Export from './Export';
|
||||
import Forget from './Forget';
|
||||
import ImportQr from './ImportQr';
|
||||
import ImportSeed from './ImportSeed';
|
||||
import Metadata from './Metadata';
|
||||
import Signing from './Signing';
|
||||
import Welcome from './Welcome';
|
||||
|
||||
@ -51,6 +52,7 @@ export default function Popup (): React.ReactElement<{}> {
|
||||
const [authRequests, setAuthRequests] = useState<null | AuthorizeRequest[]>(null);
|
||||
const [cameraOn, setCameraOn] = useState(settings.get().camera === 'on');
|
||||
const [mediaAllowed, setMediaAllowed] = useState(false);
|
||||
const [metaRequests, setMetaRequests] = useState<null | MetadataRequest[]>(null);
|
||||
const [signRequests, setSignRequests] = useState<null | SigningRequest[]>(null);
|
||||
const [isWelcomeDone, setWelcomeDone] = useState(false);
|
||||
|
||||
@ -65,8 +67,9 @@ export default function Popup (): React.ReactElement<{}> {
|
||||
useEffect((): void => {
|
||||
Promise.all([
|
||||
subscribeAccounts(setAccounts),
|
||||
subscribeAuthorize(setAuthRequests),
|
||||
subscribeSigning(setSignRequests)
|
||||
subscribeAuthorizeRequests(setAuthRequests),
|
||||
subscribeMetadataRequests(setMetaRequests),
|
||||
subscribeSigningRequests(setSignRequests)
|
||||
]).catch((error: Error) => console.error(error));
|
||||
|
||||
settings.on('change', ({ camera }): void => setCameraOn(camera === 'on'));
|
||||
@ -81,29 +84,33 @@ export default function Popup (): React.ReactElement<{}> {
|
||||
const Root = isWelcomeDone
|
||||
? authRequests && authRequests.length
|
||||
? Authorize
|
||||
: signRequests && signRequests.length
|
||||
? Signing
|
||||
: Accounts
|
||||
: metaRequests && metaRequests.length
|
||||
? Metadata
|
||||
: signRequests && signRequests.length
|
||||
? Signing
|
||||
: Accounts
|
||||
: Welcome;
|
||||
|
||||
return (
|
||||
<Loading>{accounts && authRequests && signRequests && (
|
||||
<Loading>{accounts && authRequests && metaRequests && signRequests && (
|
||||
<ActionContext.Provider value={_onAction}>
|
||||
<AccountContext.Provider value={accounts}>
|
||||
<AuthorizeContext.Provider value={authRequests}>
|
||||
<AuthorizeReqContext.Provider value={authRequests}>
|
||||
<MediaContext.Provider value={cameraOn && mediaAllowed}>
|
||||
<SigningContext.Provider value={signRequests}>
|
||||
<Switch>
|
||||
<Route path='/account/create' component={CreateAccount} />
|
||||
<Route path='/account/forget/:address' component={Forget} />
|
||||
<Route path='/account/export/:address' component={Export} />
|
||||
<Route path='/account/import-qr' component={ImportQr} />
|
||||
<Route path='/account/import-seed' component={ImportSeed} />
|
||||
<Route exact path='/' component={Root} />
|
||||
</Switch>
|
||||
</SigningContext.Provider>
|
||||
<MetadataReqContext.Provider value={metaRequests}>
|
||||
<SigningReqContext.Provider value={signRequests}>
|
||||
<Switch>
|
||||
<Route path='/account/create' component={CreateAccount} />
|
||||
<Route path='/account/forget/:address' component={Forget} />
|
||||
<Route path='/account/export/:address' component={Export} />
|
||||
<Route path='/account/import-qr' component={ImportQr} />
|
||||
<Route path='/account/import-seed' component={ImportSeed} />
|
||||
<Route exact path='/' component={Root} />
|
||||
</Switch>
|
||||
</SigningReqContext.Provider>
|
||||
</MetadataReqContext.Provider>
|
||||
</MediaContext.Provider>
|
||||
</AuthorizeContext.Provider>
|
||||
</AuthorizeReqContext.Provider>
|
||||
</AccountContext.Provider>
|
||||
</ActionContext.Provider>
|
||||
)}</Loading>
|
||||
|
||||
@ -7,16 +7,17 @@ import { Chain } from '@polkadot/extension-chains/types';
|
||||
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import findChain from '@polkadot/extension-chains';
|
||||
import settings from '@polkadot/ui-settings';
|
||||
import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';
|
||||
|
||||
import { AccountContext } from './contexts';
|
||||
import Identicon from '@polkadot/extension-ui/components/Identicon';
|
||||
import Svg from '@polkadot/extension-ui/components/Svg';
|
||||
import Menu from '@polkadot/extension-ui/components/Menu';
|
||||
import Identicon from './Identicon';
|
||||
import Svg from './Svg';
|
||||
import Menu from './Menu';
|
||||
|
||||
import DetailsImg from '../assets/details.svg';
|
||||
import { useOutsideClick } from '@polkadot/extension-ui/hooks';
|
||||
import useOutsideClick from '../hooks/useOutsideClick';
|
||||
import useMetadata from '../hooks/useMetadata';
|
||||
|
||||
interface Props {
|
||||
address?: string | null;
|
||||
@ -29,7 +30,6 @@ interface Props {
|
||||
|
||||
interface Recoded {
|
||||
account: AccountJson | null;
|
||||
chain: Chain | null;
|
||||
formatted: string | null;
|
||||
prefix: number;
|
||||
}
|
||||
@ -44,19 +44,17 @@ function findAccount (accounts: AccountJson[], publicKey: Uint8Array): AccountJs
|
||||
}
|
||||
|
||||
// recodes an supplied address using the prefix/genesisHash, include the actual saved account & chain
|
||||
function recodeAddress (address: string, accounts: AccountJson[], genesisHash?: string | null): Recoded {
|
||||
function recodeAddress (address: string, accounts: AccountJson[], chain: Chain | null): Recoded {
|
||||
// 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?.genesisHash || genesisHash);
|
||||
const prefix = settings.prefix === -1 ? chain.ss58Format : settings.prefix;
|
||||
const prefix = settings.prefix === -1 ? (chain?.ss58Format || 42) : settings.prefix;
|
||||
|
||||
// always allow the actual settings to override the display
|
||||
return {
|
||||
account,
|
||||
chain,
|
||||
formatted: encodeAddress(publicKey, prefix),
|
||||
prefix
|
||||
};
|
||||
@ -66,7 +64,8 @@ const ACCOUNTS_SCREEN_HEIGHT = 500;
|
||||
|
||||
function Address ({ address, className, children, genesisHash, name, actions }: Props): React.ReactElement<Props> {
|
||||
const accounts = useContext(AccountContext);
|
||||
const [{ account, chain, formatted, prefix }, setRecoded] = useState<Recoded>({ account: null, chain: null, formatted: null, prefix: 42 });
|
||||
const chain = useMetadata(genesisHash);
|
||||
const [{ account, formatted, prefix }, setRecoded] = useState<Recoded>({ account: null, formatted: null, prefix: 42 });
|
||||
const [showActionsMenu, setShowActionsMenu] = useState(false);
|
||||
const [moveMenuUp, setIsMovedMenu] = useState(false);
|
||||
const actionsRef = useRef<HTMLDivElement>(null);
|
||||
@ -75,9 +74,9 @@ function Address ({ address, className, children, genesisHash, name, actions }:
|
||||
|
||||
useEffect((): void => {
|
||||
address && setRecoded(
|
||||
recodeAddress(address, accounts, genesisHash)
|
||||
recodeAddress(address, accounts, chain)
|
||||
);
|
||||
}, [accounts, address, genesisHash]);
|
||||
}, [accounts, address, chain]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showActionsMenu) {
|
||||
@ -183,7 +182,7 @@ const Settings = styled.div`
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 25%;
|
||||
|
||||
@ -8,7 +8,7 @@ import styled from 'styled-components';
|
||||
import ArrowLeftImage from '../assets/arrowLeft.svg';
|
||||
import logo from '../assets/pjs.svg';
|
||||
import gear from '../assets/gear.svg';
|
||||
import { useOutsideClick } from '../hooks';
|
||||
import useOutsideClick from '../hooks/useOutsideClick';
|
||||
import Link from './Link';
|
||||
import Settings from './Settings';
|
||||
import Svg from './Svg';
|
||||
|
||||
@ -39,7 +39,7 @@ const Slider = styled.span`
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
content: '';
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
|
||||
74
packages/extension-ui/src/components/Table.tsx
Normal file
74
packages/extension-ui/src/components/Table.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2019-2020 @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';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
isFull?: boolean;
|
||||
}
|
||||
|
||||
function Table ({ children, className, isFull }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<table className={`${className} ${isFull && 'isFull'}`}>
|
||||
<tbody>
|
||||
{children}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Table)`
|
||||
&.isFull {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
border: 0;
|
||||
display: block;
|
||||
font-size: ${({ theme }): string => theme.labelFontSize};
|
||||
line-height: ${({ theme }): string => theme.labelLineHeight};
|
||||
margin-bottom: 1rem;
|
||||
|
||||
td.data {
|
||||
max-width: 0;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
|
||||
pre {
|
||||
font-family: inherit;
|
||||
font-size: 0.75rem;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
td.label {
|
||||
opacity: 0.5;
|
||||
padding: 0 0.5rem;
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
details {
|
||||
cursor: pointer;
|
||||
max-width: 24rem;
|
||||
|
||||
&[open] summary {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
summary {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
`);
|
||||
@ -2,7 +2,7 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountJson, AuthorizeRequest, SigningRequest } from '@polkadot/extension-base/background/types';
|
||||
import { AccountJson, AuthorizeRequest, MetadataRequest, SigningRequest } from '@polkadot/extension-base/background/types';
|
||||
|
||||
import React from 'react';
|
||||
import { AvailableThemes } from '.';
|
||||
@ -12,16 +12,18 @@ const noop = (): void => undefined;
|
||||
|
||||
const AccountContext = React.createContext<AccountJson[]>([]);
|
||||
const ActionContext = React.createContext<(to?: string) => void>(noop);
|
||||
const AuthorizeContext = React.createContext<AuthorizeRequest[]>([]);
|
||||
const AuthorizeReqContext = React.createContext<AuthorizeRequest[]>([]);
|
||||
const MediaContext = React.createContext<boolean>(false);
|
||||
const SigningContext = React.createContext<SigningRequest[]>([]);
|
||||
const MetadataReqContext = React.createContext<MetadataRequest[]>([]);
|
||||
const SigningReqContext = React.createContext<SigningRequest[]>([]);
|
||||
const ThemeSwitchContext = React.createContext<(theme: AvailableThemes) => void>(noop);
|
||||
|
||||
export {
|
||||
AccountContext,
|
||||
ActionContext,
|
||||
AuthorizeContext,
|
||||
AuthorizeReqContext,
|
||||
MediaContext,
|
||||
SigningContext,
|
||||
MetadataReqContext,
|
||||
SigningReqContext,
|
||||
ThemeSwitchContext
|
||||
};
|
||||
|
||||
@ -24,6 +24,7 @@ export { default as Menu } from './Menu';
|
||||
export { default as MnemonicSeed } from './MnemonicSeed';
|
||||
export { default as Settings } from './Settings';
|
||||
export { default as Svg } from './Svg';
|
||||
export { default as Table } from './Table';
|
||||
export { default as TextAreaWithLabel } from './TextAreaWithLabel';
|
||||
export { default as Title } from './Title';
|
||||
export { default as VerticalSpace } from './VerticalSpace';
|
||||
|
||||
19
packages/extension-ui/src/hooks/useMetadata.ts
Normal file
19
packages/extension-ui/src/hooks/useMetadata.ts
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2019-2020 @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 { Chain } from '@polkadot/extension-chains/types';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { getMetadata } from '../messaging';
|
||||
|
||||
export default function useMetadata (genesisHash?: string | null): Chain | null {
|
||||
const [chain, setChain] = useState<Chain | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
getMetadata(genesisHash).then(setChain);
|
||||
}, [genesisHash]);
|
||||
|
||||
return chain;
|
||||
}
|
||||
@ -4,16 +4,18 @@
|
||||
|
||||
import { useEffect, RefObject } from 'react';
|
||||
|
||||
export const useOutsideClick = (ref: RefObject<HTMLDivElement>, callback: () => void): void => {
|
||||
export default function useOutsideClick (ref: RefObject<HTMLDivElement>, callback: () => void): void {
|
||||
const handleClick = (e: MouseEvent): void => {
|
||||
if (ref.current && !ref.current.contains(e.target as HTMLInputElement)) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('click', handleClick);
|
||||
|
||||
return (): void => {
|
||||
document.removeEventListener('click', handleClick);
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -2,11 +2,13 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountJson, AuthorizeRequest, SigningRequest, RequestTypes, MessageTypes, ResponseTypes, SeedLengths, SubscriptionMessageTypes, MessageTypesWithNullRequest, MessageTypesWithNoSubscriptions, MessageTypesWithSubscriptions } from '@polkadot/extension-base/background/types';
|
||||
import { AccountJson, AuthorizeRequest, SigningRequest, RequestTypes, MessageTypes, ResponseTypes, SeedLengths, SubscriptionMessageTypes, MetadataRequest, MessageTypesWithNullRequest, MessageTypesWithNoSubscriptions, MessageTypesWithSubscriptions } from '@polkadot/extension-base/background/types';
|
||||
import { Chain } from '@polkadot/extension-chains/types';
|
||||
import { KeypairType } from '@polkadot/util-crypto/types';
|
||||
|
||||
import extension from 'extensionizer';
|
||||
import { PORT_EXTENSION } from '@polkadot/extension-base/defaults';
|
||||
import { findChain } from '@polkadot/extension-chains';
|
||||
|
||||
interface Handler {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ -27,7 +29,7 @@ port.onMessage.addListener((data): void => {
|
||||
const handler = handlers[data.id];
|
||||
|
||||
if (!handler) {
|
||||
console.error(`Uknown response: ${JSON.stringify(data)}`);
|
||||
console.error(`Unknown response: ${JSON.stringify(data)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -70,14 +72,14 @@ export async function forgetAccount (address: string): Promise<boolean> {
|
||||
return sendMessage('pri(accounts.forget)', { address });
|
||||
}
|
||||
|
||||
export async function rejectAuthRequest (id: string): Promise<boolean> {
|
||||
return sendMessage('pri(authorize.reject)', { id });
|
||||
}
|
||||
|
||||
export async function approveAuthRequest (id: string): Promise<boolean> {
|
||||
return sendMessage('pri(authorize.approve)', { id });
|
||||
}
|
||||
|
||||
export async function approveMetaRequest (id: string): Promise<boolean> {
|
||||
return sendMessage('pri(metadata.approve)', { id });
|
||||
}
|
||||
|
||||
export async function cancelSignRequest (id: string): Promise<boolean> {
|
||||
return sendMessage('pri(signing.cancel)', { id });
|
||||
}
|
||||
@ -102,16 +104,34 @@ export async function createSeed (length?: SeedLengths, type?: KeypairType): Pro
|
||||
return sendMessage('pri(seed.create)', { length, type });
|
||||
}
|
||||
|
||||
export async function getMetadata (genesisHash?: string | null): Promise<Chain | null> {
|
||||
const definitions = await sendMessage('pri(metadata.list)');
|
||||
|
||||
return findChain(definitions, genesisHash);
|
||||
}
|
||||
|
||||
export async function rejectAuthRequest (id: string): Promise<boolean> {
|
||||
return sendMessage('pri(authorize.reject)', { id });
|
||||
}
|
||||
|
||||
export async function rejectMetaRequest (id: string): Promise<boolean> {
|
||||
return sendMessage('pri(metadata.reject)', { id });
|
||||
}
|
||||
|
||||
export async function subscribeAccounts (cb: (accounts: AccountJson[]) => void): Promise<boolean> {
|
||||
return sendMessage('pri(accounts.subscribe)', null, cb);
|
||||
}
|
||||
|
||||
export async function subscribeAuthorize (cb: (accounts: AuthorizeRequest[]) => void): Promise<boolean> {
|
||||
return sendMessage('pri(authorize.subscribe)', null, cb);
|
||||
export async function subscribeAuthorizeRequests (cb: (accounts: AuthorizeRequest[]) => void): Promise<boolean> {
|
||||
return sendMessage('pri(authorize.requests)', null, cb);
|
||||
}
|
||||
|
||||
export async function subscribeSigning (cb: (accounts: SigningRequest[]) => void): Promise<boolean> {
|
||||
return sendMessage('pri(signing.subscribe)', null, cb);
|
||||
export async function subscribeMetadataRequests (cb: (accounts: MetadataRequest[]) => void): Promise<boolean> {
|
||||
return sendMessage('pri(metadata.requests)', null, cb);
|
||||
}
|
||||
|
||||
export async function subscribeSigningRequests (cb: (accounts: SigningRequest[]) => void): Promise<boolean> {
|
||||
return sendMessage('pri(signing.requests)', null, cb);
|
||||
}
|
||||
|
||||
export async function validateSeed (suri: string, type?: KeypairType): Promise<{ address: string; suri: string }> {
|
||||
|
||||
@ -2888,7 +2888,6 @@ __metadata:
|
||||
resolution: "@polkadot/extension-chains@workspace:packages/extension-chains"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.9.2
|
||||
edgeware-node-types: ^1.2.0
|
||||
peerDependencies:
|
||||
"@polkadot/api": "*"
|
||||
languageName: unknown
|
||||
@ -8271,13 +8270,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"edgeware-node-types@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "edgeware-node-types@npm:1.2.0"
|
||||
checksum: 2/2d04eb58ba2cef6bbe7d39726e3665e16579c42122a2e2da378977eafe07e2c615a86e95bc33293fcd25b008b64700d16ed7055faf50d3ddbaaa2a95bcbe17d9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ee-first@npm:1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "ee-first@npm:1.1.1"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user