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
|
cp -r $SRC $DST
|
||||||
}
|
}
|
||||||
|
|
||||||
yarn polkadot-dev-build-ts
|
yarn build
|
||||||
|
|
||||||
copy_folder "extension-dapp"
|
copy_folder "extension-dapp"
|
||||||
copy_folder "extension-inject"
|
copy_folder "extension-inject"
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
"postinstall": "polkadot-dev-yarn-only",
|
"postinstall": "polkadot-dev-yarn-only",
|
||||||
"start": "yarn watch",
|
"start": "yarn watch",
|
||||||
"test": "polkadot-dev-run-test",
|
"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": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.9.0",
|
"@babel/core": "^7.9.0",
|
||||||
|
|||||||
@ -2,8 +2,9 @@
|
|||||||
// This software may be modified and distributed under the terms
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// 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 { 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 extension from 'extensionizer';
|
||||||
import keyring from '@polkadot/ui-keyring';
|
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
|
// FIXME This looks very much like what we have in accounts
|
||||||
private authorizeSubscribe (id: string, port: chrome.runtime.Port): boolean {
|
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 =>
|
const subscription = this.#state.authSubject.subscribe((requests: AuthorizeRequest[]): void =>
|
||||||
cb(requests)
|
cb(requests)
|
||||||
);
|
);
|
||||||
@ -121,6 +122,50 @@ export default class Extension {
|
|||||||
return true;
|
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 {
|
private seedCreate ({ length = SEED_DEFAULT_LENGTH, type }: RequestSeedCreate): ResponseSeedCreate {
|
||||||
const seed = mnemonicGenerate(length);
|
const seed = mnemonicGenerate(length);
|
||||||
|
|
||||||
@ -199,7 +244,7 @@ export default class Extension {
|
|||||||
|
|
||||||
// FIXME This looks very much like what we have in authorization
|
// FIXME This looks very much like what we have in authorization
|
||||||
private signingSubscribe (id: string, port: chrome.runtime.Port): boolean {
|
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 =>
|
const subscription = this.#state.signSubject.subscribe((requests: SigningRequest[]): void =>
|
||||||
cb(requests)
|
cb(requests)
|
||||||
);
|
);
|
||||||
@ -228,9 +273,9 @@ export default class Extension {
|
|||||||
return this.authorizeApprove(request as RequestAuthorizeApprove);
|
return this.authorizeApprove(request as RequestAuthorizeApprove);
|
||||||
|
|
||||||
case 'pri(authorize.reject)':
|
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);
|
return this.authorizeSubscribe(id, port);
|
||||||
|
|
||||||
case 'pri(accounts.create.external)':
|
case 'pri(accounts.create.external)':
|
||||||
@ -251,6 +296,18 @@ export default class Extension {
|
|||||||
case 'pri(accounts.subscribe)':
|
case 'pri(accounts.subscribe)':
|
||||||
return this.accountsSubscribe(id, port);
|
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)':
|
case 'pri(seed.create)':
|
||||||
return this.seedCreate(request as RequestSeedCreate);
|
return this.seedCreate(request as RequestSeedCreate);
|
||||||
|
|
||||||
@ -266,7 +323,7 @@ export default class Extension {
|
|||||||
case 'pri(signing.cancel)':
|
case 'pri(signing.cancel)':
|
||||||
return this.signingCancel(request as RequestSigningCancel);
|
return this.signingCancel(request as RequestSigningCancel);
|
||||||
|
|
||||||
case 'pri(signing.subscribe)':
|
case 'pri(signing.requests)':
|
||||||
return this.signingSubscribe(id, port);
|
return this.signingSubscribe(id, port);
|
||||||
|
|
||||||
case 'pri(window.open)':
|
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
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// 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 { JsonRpcResponse, ProviderInterface, ProviderInterfaceCallback } from '@polkadot/rpc-provider/types';
|
||||||
|
import { AccountJson, AuthorizeRequest, MetadataRequest, RequestAuthorizeTab, RequestRpcSend, RequestRpcSubscribe, RequestSign, ResponseRpcListProviders, ResponseSigning, SigningRequest, RequestRpcUnsubscribe } from '../types';
|
||||||
import { AccountJson, AuthorizeRequest, RequestAuthorizeTab, RequestRpcSend, RequestRpcSubscribe, RequestSign, ResponseRpcListProviders, ResponseSigning, SigningRequest, RequestRpcUnsubscribe } from '../types';
|
|
||||||
|
|
||||||
import extension from 'extensionizer';
|
import extension from 'extensionizer';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { assert } from '@polkadot/util';
|
import { assert } from '@polkadot/util';
|
||||||
|
|
||||||
|
import MetadataStore from './MetadataStore';
|
||||||
|
|
||||||
interface AuthRequest {
|
interface AuthRequest {
|
||||||
id: string;
|
id: string;
|
||||||
idStr: string;
|
idStr: string;
|
||||||
@ -28,6 +30,14 @@ type AuthUrls = Record<string, {
|
|||||||
url: 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
|
// List of providers passed into constructor. This is the list of providers
|
||||||
// exposed by the extension.
|
// exposed by the extension.
|
||||||
type Providers = Record<string, {
|
type Providers = Record<string, {
|
||||||
@ -68,9 +78,13 @@ export default class State {
|
|||||||
|
|
||||||
readonly #authRequests: Record<string, AuthRequest> = {};
|
readonly #authRequests: Record<string, AuthRequest> = {};
|
||||||
|
|
||||||
|
readonly #metaStore = new MetadataStore();
|
||||||
|
|
||||||
// Map of providers currently injected in tabs
|
// Map of providers currently injected in tabs
|
||||||
readonly #injectedProviders: Map<chrome.runtime.Port, ProviderInterface> = new Map();
|
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
|
// Map of all providers exposed by the extension, they are retrievable by key
|
||||||
readonly #providers: Providers;
|
readonly #providers: Providers;
|
||||||
|
|
||||||
@ -78,26 +92,34 @@ export default class State {
|
|||||||
|
|
||||||
#windows: number[] = [];
|
#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 = {}) {
|
constructor (providers: Providers = {}) {
|
||||||
this.#providers = providers;
|
this.#providers = providers;
|
||||||
|
|
||||||
|
this.#metaStore.all((key: string, def: MetadataDef): void => {
|
||||||
|
if (key.startsWith('metadata:')) {
|
||||||
|
addMetadata(def);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public get hasAuthRequests (): boolean {
|
public get knownMetadata (): MetadataDef[] {
|
||||||
return this.numAuthRequests === 0;
|
return knownMetadata();
|
||||||
}
|
|
||||||
|
|
||||||
public get hasSignRequests (): boolean {
|
|
||||||
return this.numSignRequests === 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get numAuthRequests (): number {
|
public get numAuthRequests (): number {
|
||||||
return Object.keys(this.#authRequests).length;
|
return Object.keys(this.#authRequests).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get numMetaRequests (): number {
|
||||||
|
return Object.keys(this.#metaRequests).length;
|
||||||
|
}
|
||||||
|
|
||||||
public get numSignRequests (): number {
|
public get numSignRequests (): number {
|
||||||
return Object.keys(this.#signRequests).length;
|
return Object.keys(this.#signRequests).length;
|
||||||
}
|
}
|
||||||
@ -108,6 +130,12 @@ export default class State {
|
|||||||
.map(({ id, request, url }): AuthorizeRequest => ({ id, request, url }));
|
.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[] {
|
public get allSignRequests (): SigningRequest[] {
|
||||||
return Object
|
return Object
|
||||||
.values(this.#signRequests)
|
.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 => {
|
private signComplete = (id: string, fn: Function): (result: ResponseSigning | Error) => void => {
|
||||||
return (result: ResponseSigning | Error): void => {
|
return (result: ResponseSigning | Error): void => {
|
||||||
delete this.#signRequests[id];
|
delete this.#signRequests[id];
|
||||||
@ -168,11 +205,14 @@ export default class State {
|
|||||||
|
|
||||||
private updateIcon (shouldClose?: boolean): void {
|
private updateIcon (shouldClose?: boolean): void {
|
||||||
const authCount = this.numAuthRequests;
|
const authCount = this.numAuthRequests;
|
||||||
|
const metaCount = this.numMetaRequests;
|
||||||
const signCount = this.numSignRequests;
|
const signCount = this.numSignRequests;
|
||||||
const text = (
|
const text = (
|
||||||
authCount
|
authCount
|
||||||
? 'Auth'
|
? 'Auth'
|
||||||
: (signCount ? `${signCount}` : '')
|
: metaCount
|
||||||
|
? 'Meta'
|
||||||
|
: (signCount ? `${signCount}` : '')
|
||||||
);
|
);
|
||||||
|
|
||||||
extension.browserAction.setBadgeText({ text });
|
extension.browserAction.setBadgeText({ text });
|
||||||
@ -187,6 +227,11 @@ export default class State {
|
|||||||
this.updateIcon(shouldClose);
|
this.updateIcon(shouldClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateIconMeta (shouldClose?: boolean): void {
|
||||||
|
this.metaSubject.next(this.allMetaRequests);
|
||||||
|
this.updateIcon(shouldClose);
|
||||||
|
}
|
||||||
|
|
||||||
private updateIconSign (shouldClose?: boolean): void {
|
private updateIconSign (shouldClose?: boolean): void {
|
||||||
this.signSubject.next(this.allSignRequests);
|
this.signSubject.next(this.allSignRequests);
|
||||||
this.updateIcon(shouldClose);
|
this.updateIcon(shouldClose);
|
||||||
@ -198,7 +243,7 @@ export default class State {
|
|||||||
if (this.#authUrls[idStr]) {
|
if (this.#authUrls[idStr]) {
|
||||||
assert(this.#authUrls[idStr].isAllowed, `The source ${url} is not allowed to interact with this extension`);
|
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 => {
|
return new Promise((resolve, reject): void => {
|
||||||
@ -227,10 +272,31 @@ export default class State {
|
|||||||
return true;
|
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 {
|
public getAuthRequest (id: string): AuthRequest {
|
||||||
return this.#authRequests[id];
|
return this.#authRequests[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getMetaRequest (id: string): MetaRequest {
|
||||||
|
return this.#metaRequests[id];
|
||||||
|
}
|
||||||
|
|
||||||
public getSignRequest (id: string): SignRequest {
|
public getSignRequest (id: string): SignRequest {
|
||||||
return this.#signRequests[id];
|
return this.#signRequests[id];
|
||||||
}
|
}
|
||||||
@ -303,6 +369,12 @@ export default class State {
|
|||||||
return provider.unsubscribe(request.type, request.method, request.subscriptionId);
|
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> {
|
public sign (url: string, request: RequestSign, account: AccountJson): Promise<ResponseSigning> {
|
||||||
const id = getId();
|
const id = getId();
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
// This software may be modified and distributed under the terms
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// 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 { KeyringPair } from '@polkadot/keyring/types';
|
||||||
import { JsonRpcResponse } from '@polkadot/rpc-provider/types';
|
import { JsonRpcResponse } from '@polkadot/rpc-provider/types';
|
||||||
import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/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 keyring from '@polkadot/ui-keyring';
|
||||||
import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
|
import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
|
||||||
import { assert } from '@polkadot/util';
|
import { assert } from '@polkadot/util';
|
||||||
|
|
||||||
import RequestBytesSign from '../RequestBytesSign';
|
import RequestBytesSign from '../RequestBytesSign';
|
||||||
import RequestExtrinsicSign from '../RequestExtrinsicSign';
|
import RequestExtrinsicSign from '../RequestExtrinsicSign';
|
||||||
|
|
||||||
import State from './State';
|
import State from './State';
|
||||||
import { createSubscription, unsubscribe } from './subscriptions';
|
import { createSubscription, unsubscribe } from './subscriptions';
|
||||||
|
|
||||||
@ -57,22 +57,38 @@ export default class Tabs {
|
|||||||
|
|
||||||
private getSigningPair (address: string): KeyringPair {
|
private getSigningPair (address: string): KeyringPair {
|
||||||
const pair = keyring.getPair(address);
|
const pair = keyring.getPair(address);
|
||||||
|
|
||||||
assert(pair, 'Unable to find keypair');
|
assert(pair, 'Unable to find keypair');
|
||||||
|
|
||||||
return pair;
|
return pair;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bytesSign (url: string, request: SignerPayloadRaw): Promise<ResponseSigning> {
|
private bytesSign (url: string, request: SignerPayloadRaw): Promise<ResponseSigning> {
|
||||||
const address = request.address;
|
const address = request.address;
|
||||||
const pair = this.getSigningPair(address);
|
const pair = this.getSigningPair(address);
|
||||||
|
|
||||||
return this.#state.sign(url, new RequestBytesSign(request), { address, ...pair.meta });
|
return this.#state.sign(url, new RequestBytesSign(request), { address, ...pair.meta });
|
||||||
}
|
}
|
||||||
|
|
||||||
private extrinsicSign (url: string, request: SignerPayloadJSON): Promise<ResponseSigning> {
|
private extrinsicSign (url: string, request: SignerPayloadJSON): Promise<ResponseSigning> {
|
||||||
const address = request.address;
|
const address = request.address;
|
||||||
const pair = this.getSigningPair(address);
|
const pair = this.getSigningPair(address);
|
||||||
|
|
||||||
return this.#state.sign(url, new RequestExtrinsicSign(request), { address, ...pair.meta });
|
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> {
|
private rpcListProviders (): Promise<ResponseRpcListProviders> {
|
||||||
return this.#state.rpcListProviders();
|
return this.#state.rpcListProviders();
|
||||||
}
|
}
|
||||||
@ -135,6 +151,12 @@ export default class Tabs {
|
|||||||
case 'pub(extrinsic.sign)':
|
case 'pub(extrinsic.sign)':
|
||||||
return this.extrinsicSign(url, request as SignerPayloadJSON);
|
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)':
|
case 'pub(rpc.listProviders)':
|
||||||
return this.rpcListProviders();
|
return this.rpcListProviders();
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
// This software may be modified and distributed under the terms
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// 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 { JsonRpcResponse } from '@polkadot/rpc-provider/types';
|
||||||
import { KeypairType } from '@polkadot/util-crypto/types';
|
import { KeypairType } from '@polkadot/util-crypto/types';
|
||||||
import { KeyringPair } from '@polkadot/keyring/types';
|
import { KeyringPair } from '@polkadot/keyring/types';
|
||||||
@ -36,6 +36,12 @@ export interface AuthorizeRequest {
|
|||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MetadataRequest {
|
||||||
|
id: string;
|
||||||
|
request: MetadataDef;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SigningRequest {
|
export interface SigningRequest {
|
||||||
account: AccountJson;
|
account: AccountJson;
|
||||||
id: string;
|
id: string;
|
||||||
@ -54,13 +60,17 @@ export interface RequestSignatures {
|
|||||||
'pri(accounts.subscribe)': [RequestAccountSubscribe, boolean, AccountJson[]];
|
'pri(accounts.subscribe)': [RequestAccountSubscribe, boolean, AccountJson[]];
|
||||||
'pri(authorize.approve)': [RequestAuthorizeApprove, boolean];
|
'pri(authorize.approve)': [RequestAuthorizeApprove, boolean];
|
||||||
'pri(authorize.reject)': [RequestAuthorizeReject, 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.create)': [RequestSeedCreate, ResponseSeedCreate];
|
||||||
'pri(seed.validate)': [RequestSeedValidate, ResponseSeedValidate];
|
'pri(seed.validate)': [RequestSeedValidate, ResponseSeedValidate];
|
||||||
'pri(signing.approve.password)': [RequestSigningApprovePassword, boolean];
|
'pri(signing.approve.password)': [RequestSigningApprovePassword, boolean];
|
||||||
'pri(signing.approve.signature)': [RequestSigningApproveSignature, boolean];
|
'pri(signing.approve.signature)': [RequestSigningApproveSignature, boolean];
|
||||||
'pri(signing.cancel)': [RequestSigningCancel, boolean];
|
'pri(signing.cancel)': [RequestSigningCancel, boolean];
|
||||||
'pri(signing.subscribe)': [RequestSigningSubscribe, boolean, SigningRequest[]];
|
'pri(signing.requests)': [RequestSigningSubscribe, boolean, SigningRequest[]];
|
||||||
'pri(window.open)': [null, boolean];
|
'pri(window.open)': [null, boolean];
|
||||||
// public/external requests, i.e. from a page
|
// public/external requests, i.e. from a page
|
||||||
'pub(accounts.list)': [RequestAccountList, InjectedAccount[]];
|
'pub(accounts.list)': [RequestAccountList, InjectedAccount[]];
|
||||||
@ -68,6 +78,8 @@ export interface RequestSignatures {
|
|||||||
'pub(authorize.tab)': [RequestAuthorizeTab, null];
|
'pub(authorize.tab)': [RequestAuthorizeTab, null];
|
||||||
'pub(bytes.sign)': [SignerPayloadRaw, ResponseSigning];
|
'pub(bytes.sign)': [SignerPayloadRaw, ResponseSigning];
|
||||||
'pub(extrinsic.sign)': [SignerPayloadJSON, ResponseSigning];
|
'pub(extrinsic.sign)': [SignerPayloadJSON, ResponseSigning];
|
||||||
|
'pub(metadata.list)': [null, InjectedMetadataKnown[]];
|
||||||
|
'pub(metadata.provide)': [MetadataDef, boolean];
|
||||||
'pub(rpc.listProviders)': [void, ResponseRpcListProviders];
|
'pub(rpc.listProviders)': [void, ResponseRpcListProviders];
|
||||||
'pub(rpc.send)': [RequestRpcSend, JsonRpcResponse];
|
'pub(rpc.send)': [RequestRpcSend, JsonRpcResponse];
|
||||||
'pub(rpc.startProvider)': [string, ProviderMeta];
|
'pub(rpc.startProvider)': [string, ProviderMeta];
|
||||||
@ -107,6 +119,16 @@ export interface RequestAuthorizeReject {
|
|||||||
|
|
||||||
export type RequestAuthorizeSubscribe = null;
|
export type RequestAuthorizeSubscribe = null;
|
||||||
|
|
||||||
|
export interface RequestMetadataApprove {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RequestMetadataReject {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RequestMetadataSubscribe = null;
|
||||||
|
|
||||||
export interface RequestAccountCreateExternal {
|
export interface RequestAccountCreateExternal {
|
||||||
address: string;
|
address: string;
|
||||||
genesisHash?: string | null;
|
genesisHash?: string | null;
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
import { InjectedAccounts, InjectedAccount, Unsubcall } from '@polkadot/extension-inject/types';
|
import { InjectedAccounts, InjectedAccount, Unsubcall } from '@polkadot/extension-inject/types';
|
||||||
import { SendRequest } from './types';
|
import { SendRequest } from './types';
|
||||||
|
|
||||||
|
// External to class, this.# is not private enough (yet)
|
||||||
let sendRequest: SendRequest;
|
let sendRequest: SendRequest;
|
||||||
|
|
||||||
export default class Accounts implements InjectedAccounts {
|
export default class Accounts implements InjectedAccounts {
|
||||||
|
|||||||
@ -7,17 +7,21 @@ import { Injected } from '@polkadot/extension-inject/types';
|
|||||||
import { SendRequest } from './types';
|
import { SendRequest } from './types';
|
||||||
|
|
||||||
import Accounts from './Accounts';
|
import Accounts from './Accounts';
|
||||||
|
import Metadata from './Metadata';
|
||||||
import Signer from './Signer';
|
import Signer from './Signer';
|
||||||
|
|
||||||
export default class implements Injected {
|
export default class implements Injected {
|
||||||
public readonly accounts: Accounts;
|
public readonly accounts: Accounts;
|
||||||
|
|
||||||
|
public readonly metadata: Metadata;
|
||||||
|
|
||||||
public readonly provider: PostMessageProvider;
|
public readonly provider: PostMessageProvider;
|
||||||
|
|
||||||
public readonly signer: Signer;
|
public readonly signer: Signer;
|
||||||
|
|
||||||
constructor (sendRequest: SendRequest) {
|
constructor (sendRequest: SendRequest) {
|
||||||
this.accounts = new Accounts(sendRequest);
|
this.accounts = new Accounts(sendRequest);
|
||||||
|
this.metadata = new Metadata(sendRequest);
|
||||||
this.provider = new PostMessageProvider(sendRequest);
|
this.provider = new PostMessageProvider(sendRequest);
|
||||||
this.signer = new Signer(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;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// External to class, this.# is not private enough (yet)
|
||||||
|
let sendRequest: SendRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name PostMessageProvider
|
* @name PostMessageProvider
|
||||||
*
|
*
|
||||||
@ -32,8 +35,6 @@ export default class PostMessageProvider implements InjectedProvider {
|
|||||||
// Whether or not the actual extension background provider is connected
|
// Whether or not the actual extension background provider is connected
|
||||||
#isConnected = false
|
#isConnected = false
|
||||||
|
|
||||||
readonly #sendRequest: SendRequest;
|
|
||||||
|
|
||||||
// Subscription IDs are (historically) not guaranteed to be globally unique;
|
// Subscription IDs are (historically) not guaranteed to be globally unique;
|
||||||
// only unique for a given subscription method; which is why we identify
|
// only unique for a given subscription method; which is why we identify
|
||||||
// the subscriptions based on subscription id + type
|
// 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} sendRequest The function to be called to send requests to the node
|
||||||
* @param {function} subscriptionNotificationHandler Channel for receiving subscription messages
|
* @param {function} subscriptionNotificationHandler Channel for receiving subscription messages
|
||||||
*/
|
*/
|
||||||
public constructor (sendRequest: SendRequest) {
|
public constructor (_sendRequest: SendRequest) {
|
||||||
this.#eventemitter = new EventEmitter();
|
this.#eventemitter = new EventEmitter();
|
||||||
this.#sendRequest = sendRequest;
|
|
||||||
|
sendRequest = _sendRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Returns a clone of the object
|
* @description Returns a clone of the object
|
||||||
*/
|
*/
|
||||||
public clone (): PostMessageProvider {
|
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> {
|
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) {
|
if (subscription) {
|
||||||
const { callback, type } = subscription;
|
const { callback, type } = subscription;
|
||||||
|
|
||||||
return this.#sendRequest(
|
return sendRequest(
|
||||||
'pub(rpc.subscribe)',
|
'pub(rpc.subscribe)',
|
||||||
{ type, method, params },
|
{ type, method, params },
|
||||||
(res) => {
|
(res) => {
|
||||||
@ -114,7 +116,7 @@ export default class PostMessageProvider implements InjectedProvider {
|
|||||||
return id;
|
return id;
|
||||||
});
|
});
|
||||||
} else {
|
} 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.#isConnected = false;
|
||||||
this.#eventemitter.emit('disconnected');
|
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;
|
this.#isConnected = connected;
|
||||||
|
|
||||||
if (connected) {
|
if (connected) {
|
||||||
|
|||||||
@ -6,14 +6,12 @@ import { Signer as SignerInterface, SignerResult } from '@polkadot/api/types';
|
|||||||
import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
|
import { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
|
||||||
import { SendRequest } from './types';
|
import { SendRequest } from './types';
|
||||||
|
|
||||||
|
// External to class, this.# is not private enough (yet)
|
||||||
let sendRequest: SendRequest;
|
let sendRequest: SendRequest;
|
||||||
let nextId = 0;
|
let nextId = 0;
|
||||||
|
|
||||||
export default class Signer implements SignerInterface {
|
export default class Signer implements SignerInterface {
|
||||||
constructor (_sendRequest: SendRequest) {
|
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;
|
sendRequest = _sendRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,7 @@
|
|||||||
"author": "Jaco Greeff <jacogr@gmail.com>",
|
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||||
"license": "Apache-2",
|
"license": "Apache-2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.9.2",
|
"@babel/runtime": "^7.9.2"
|
||||||
"edgeware-node-types": "^1.2.0"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@polkadot/api": "*"
|
"@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
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// 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';
|
import { Metadata, TypeRegistry } from '@polkadot/types';
|
||||||
|
|
||||||
// imports chain details, generally metadata. For the generation of these,
|
// imports chain details, generally metadata. For the generation of these,
|
||||||
// inside the api, run `yarn chain:info --ws <url>`
|
// inside the api, run `yarn chain:info --ws <url>`
|
||||||
import edgeware from './edgeware';
|
|
||||||
import kusama from './kusama';
|
import kusama from './kusama';
|
||||||
|
|
||||||
const chains: Map<string, Chain> = new Map(
|
const definitions: Map<string, MetadataDef> = new Map(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
[kusama].map((def) => [def.genesisHash, def])
|
||||||
[edgeware, kusama].map((def) => defToChain(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();
|
const registry = new TypeRegistry();
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ -33,7 +33,8 @@ function defToChain ({ chain, genesisHash, icon, metaCalls, specVersion, ss58For
|
|||||||
tokenSymbol
|
tokenSymbol
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return [genesisHash, {
|
return {
|
||||||
|
definition,
|
||||||
genesisHash: isUnknown
|
genesisHash: isUnknown
|
||||||
? undefined
|
? undefined
|
||||||
: genesisHash,
|
: genesisHash,
|
||||||
@ -46,26 +47,21 @@ function defToChain ({ chain, genesisHash, icon, metaCalls, specVersion, ss58For
|
|||||||
ss58Format,
|
ss58Format,
|
||||||
tokenDecimals,
|
tokenDecimals,
|
||||||
tokenSymbol
|
tokenSymbol
|
||||||
}];
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const [, UNKNOWN_CHAIN] = defToChain({
|
export function findChain (definitions: MetadataDef[], genesisHash?: string | null): Chain | null {
|
||||||
chain: 'Unknown chain',
|
const def = definitions.find((def) => def.genesisHash === genesisHash);
|
||||||
genesisHash: '0x',
|
|
||||||
icon: 'polkadot',
|
|
||||||
specVersion: 0,
|
|
||||||
ss58Format: 42,
|
|
||||||
tokenDecimals: 0,
|
|
||||||
tokenSymbol: 'Unit',
|
|
||||||
types: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
export function addChainDef (def: ChainDef): void {
|
return def
|
||||||
const [genesisHash, chain] = defToChain(def);
|
? metadataExpand(def)
|
||||||
|
: null;
|
||||||
chains.set(genesisHash, chain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function findChain (genesisHash?: string | null): Chain {
|
export function addMetadata (def: MetadataDef): void {
|
||||||
return chains.get(genesisHash || '') || UNKNOWN_CHAIN;
|
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
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// 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';
|
import { Registry } from '@polkadot/types/types';
|
||||||
|
|
||||||
export interface Chain {
|
export interface Chain {
|
||||||
|
definition: MetadataDef;
|
||||||
genesisHash?: string;
|
genesisHash?: string;
|
||||||
hasMetadata: boolean;
|
hasMetadata: boolean;
|
||||||
icon: string;
|
icon: string;
|
||||||
@ -16,15 +18,3 @@ export interface Chain {
|
|||||||
tokenDecimals: number;
|
tokenDecimals: number;
|
||||||
tokenSymbol: string;
|
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
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
subscribe: (cb: (accounts: InjectedAccount[]) => any) => Unsubcall;
|
subscribe: (cb: (accounts: InjectedAccount[]) => any) => Unsubcall;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InjectedExtensionInfo {
|
export interface InjectedExtensionInfo {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
@ -47,6 +48,28 @@ export interface ProviderMeta {
|
|||||||
transport: string;
|
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 type ProviderList = Record<string, ProviderMeta>
|
||||||
|
|
||||||
export interface InjectedProvider extends ProviderInterface {
|
export interface InjectedProvider extends ProviderInterface {
|
||||||
@ -62,8 +85,9 @@ export interface InjectedProviderWithMeta {
|
|||||||
|
|
||||||
export interface Injected {
|
export interface Injected {
|
||||||
accounts: InjectedAccounts;
|
accounts: InjectedAccounts;
|
||||||
signer: InjectedSigner;
|
metadata?: InjectedMetadata;
|
||||||
provider?: InjectedProvider;
|
provider?: InjectedProvider;
|
||||||
|
signer: InjectedSigner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InjectedWindowProvider {
|
export interface InjectedWindowProvider {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import React from 'react';
|
|||||||
import Adapter from 'enzyme-adapter-react-16';
|
import Adapter from 'enzyme-adapter-react-16';
|
||||||
|
|
||||||
import { ThemeProvider } from 'styled-components';
|
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 Authorize from '.';
|
||||||
import { configure, mount, ReactWrapper } from 'enzyme';
|
import { configure, mount, ReactWrapper } from 'enzyme';
|
||||||
import { AuthorizeRequest } from '@polkadot/extension-base/background/types';
|
import { AuthorizeRequest } from '@polkadot/extension-base/background/types';
|
||||||
@ -16,11 +16,11 @@ configure({ adapter: new Adapter() });
|
|||||||
|
|
||||||
describe('Authorize', () => {
|
describe('Authorize', () => {
|
||||||
const mountAuthorize = (authorizeRequests: AuthorizeRequest[] = []): ReactWrapper => mount(
|
const mountAuthorize = (authorizeRequests: AuthorizeRequest[] = []): ReactWrapper => mount(
|
||||||
<AuthorizeContext.Provider value={authorizeRequests}>
|
<AuthorizeReqContext.Provider value={authorizeRequests}>
|
||||||
<ThemeProvider theme={themes.dark}>
|
<ThemeProvider theme={themes.dark}>
|
||||||
<Authorize />
|
<Authorize />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</AuthorizeContext.Provider>);
|
</AuthorizeReqContext.Provider>);
|
||||||
|
|
||||||
it('render component', () => {
|
it('render component', () => {
|
||||||
const wrapper = mountAuthorize();
|
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{' '}
|
An application, self-identifying as <span className='tab-name'>{origin}</span> is requesting access from{' '}
|
||||||
<a
|
<a
|
||||||
href={url}
|
href={url}
|
||||||
target="_blank"
|
target='_blank'
|
||||||
rel="noopener noreferrer"
|
rel='noopener noreferrer'
|
||||||
>
|
>
|
||||||
<span className='tab-url'>{url}</span>
|
<span className='tab-url'>{url}</span>
|
||||||
</a>.
|
</a>.
|
||||||
|
|||||||
@ -4,17 +4,17 @@
|
|||||||
|
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
import { AuthorizeContext, Header } from '../../components';
|
import { AuthorizeReqContext, Header } from '../../components';
|
||||||
import Request from './Request';
|
import Request from './Request';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export default function Authorize (): React.ReactElement<{}> {
|
export default function Authorize (): React.ReactElement<{}> {
|
||||||
const requests = useContext(AuthorizeContext);
|
const requests = useContext(AuthorizeReqContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Scroll isLastRequest={requests.length === 1}>
|
<Scroll isLastRequest={requests.length === 1}>
|
||||||
<Header text='Authorize'/>
|
<Header text='Authorize' />
|
||||||
{requests.map(({ id, request, url }, index): React.ReactNode => (
|
{requests.map(({ id, request, url }, index): React.ReactNode => (
|
||||||
<Request
|
<Request
|
||||||
authId={id}
|
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 BN from 'bn.js';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
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 { formatNumber, bnToBn } from '@polkadot/util';
|
||||||
|
|
||||||
|
import { Table } from '../../components';
|
||||||
|
import useMetadata from '../../hooks/useMetadata';
|
||||||
|
|
||||||
interface Decoded {
|
interface Decoded {
|
||||||
args: AnyJson | null;
|
args: AnyJson | null;
|
||||||
method: Call | null;
|
method: Call | null;
|
||||||
@ -25,12 +26,12 @@ interface Props {
|
|||||||
url: string;
|
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 args: AnyJson | null = null;
|
||||||
let method: Call | null = null;
|
let method: Call | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isDecoded && chain.hasMetadata && specVersion.eqn(chain.specVersion)) {
|
if (isDecoded && chain && chain.hasMetadata && specVersion.eqn(chain.specVersion)) {
|
||||||
method = chain.registry.createType('Call', data);
|
method = chain.registry.createType('Call', data);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
args = (method.toHuman() as any).args;
|
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> {
|
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 specVersion = useRef(bnToBn(hexSpec)).current;
|
||||||
const [decoded, setDecoded] = useState<Decoded>({ args: null, method: null });
|
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]);
|
}, [method, isDecoded, chain, specVersion]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className={className}>
|
<Table className={className} isFull>
|
||||||
<tbody>
|
<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>
|
<tr>
|
||||||
<td className='label'>from</td>
|
<td className='label'>tip</td>
|
||||||
<td className='data'>{url}</td>
|
<td className='data'>{formatNumber(tip)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
)}
|
||||||
<td className='label'>{chain.isUnknown ? 'genesis' : 'chain'}</td>
|
{renderMethod(method, decoded)}
|
||||||
<td className='data'>{chain.isUnknown ? genesisHash : chain.name}</td>
|
<tr>
|
||||||
</tr>
|
<td className='label'>lifetime</td>
|
||||||
<tr>
|
<td className='data'>{mortalityAsString(era, blockNumber)}</td>
|
||||||
<td className='label'>version</td>
|
</tr>
|
||||||
<td className='data'>{specVersion.toNumber()}</td>
|
</Table>
|
||||||
</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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default styled(Extrinsic)`
|
export default React.memo(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@ export default function Request ({ account: { isExternal }, request, signId, url
|
|||||||
|
|
||||||
useEffect((): void => {
|
useEffect((): void => {
|
||||||
const inner = request.inner;
|
const inner = request.inner;
|
||||||
|
|
||||||
if ((inner as SignerPayloadRaw).data) {
|
if ((inner as SignerPayloadRaw).data) {
|
||||||
setHexBytes((inner as SignerPayloadRaw).data);
|
setHexBytes((inner as SignerPayloadRaw).data);
|
||||||
setExtrinsic(null);
|
setExtrinsic(null);
|
||||||
@ -45,10 +46,10 @@ export default function Request ({ account: { isExternal }, request, signId, url
|
|||||||
}
|
}
|
||||||
}, [request]);
|
}, [request]);
|
||||||
|
|
||||||
const _onCancel = (): Promise<void> => {
|
const _onCancel = (): Promise<void> =>
|
||||||
return cancelSignRequest(signId).then((): void => onAction())
|
cancelSignRequest(signId)
|
||||||
|
.then((): void => onAction())
|
||||||
.catch((error: Error) => console.error(error));
|
.catch((error: Error) => console.error(error));
|
||||||
};
|
|
||||||
const _onSign = (password: string): Promise<void> =>
|
const _onSign = (password: string): Promise<void> =>
|
||||||
approveSignPassword(signId, password)
|
approveSignPassword(signId, password)
|
||||||
.then((): void => onAction())
|
.then((): void => onAction())
|
||||||
@ -60,6 +61,7 @@ export default function Request ({ account: { isExternal }, request, signId, url
|
|||||||
|
|
||||||
if (extrinsic !== null) {
|
if (extrinsic !== null) {
|
||||||
const payload = request.inner as SignerPayloadJSON;
|
const payload = request.inner as SignerPayloadJSON;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Address
|
<Address
|
||||||
@ -92,6 +94,7 @@ export default function Request ({ account: { isExternal }, request, signId, url
|
|||||||
);
|
);
|
||||||
} else if (hexBytes !== null) {
|
} else if (hexBytes !== null) {
|
||||||
const payload = request.inner as SignerPayloadRaw;
|
const payload = request.inner as SignerPayloadRaw;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Address address={payload.address} />
|
<Address address={payload.address} />
|
||||||
@ -105,9 +108,9 @@ export default function Request ({ account: { isExternal }, request, signId, url
|
|||||||
</SignArea>
|
</SignArea>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SignArea = styled(ButtonArea)`
|
const SignArea = styled(ButtonArea)`
|
||||||
|
|||||||
@ -10,15 +10,7 @@ import { act } from 'react-dom/test-utils';
|
|||||||
import * as messaging from '@polkadot/extension-ui/messaging';
|
import * as messaging from '@polkadot/extension-ui/messaging';
|
||||||
import { AccountJson, SigningRequest } from '@polkadot/extension-base/background/types';
|
import { AccountJson, SigningRequest } from '@polkadot/extension-base/background/types';
|
||||||
import { flushAllPromises } from '@polkadot/extension-ui/testHelpers';
|
import { flushAllPromises } from '@polkadot/extension-ui/testHelpers';
|
||||||
import {
|
import { AccountContext, ActionContext, Address, Button, Input, SigningReqContext, themes } from '@polkadot/extension-ui/components';
|
||||||
AccountContext,
|
|
||||||
ActionContext,
|
|
||||||
themes,
|
|
||||||
SigningContext,
|
|
||||||
Address,
|
|
||||||
Input,
|
|
||||||
Button
|
|
||||||
} from '@polkadot/extension-ui/components';
|
|
||||||
import { ThemeProvider } from 'styled-components';
|
import { ThemeProvider } from 'styled-components';
|
||||||
import Signing from '.';
|
import Signing from '.';
|
||||||
import TransactionIndex from './TransactionIndex';
|
import TransactionIndex from './TransactionIndex';
|
||||||
@ -52,9 +44,9 @@ describe('Signing requests', () => {
|
|||||||
emitter.on('request', setRequests);
|
emitter.on('request', setRequests);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SigningContext.Provider value={requests}>
|
<SigningReqContext.Provider value={requests}>
|
||||||
<Signing />
|
<Signing />
|
||||||
</SigningContext.Provider>
|
</SigningReqContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const mountComponent = async (): Promise<void> => {
|
const mountComponent = async (): Promise<void> => {
|
||||||
|
|||||||
@ -2,16 +2,24 @@
|
|||||||
// This software may be modified and distributed under the terms
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// 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 Request from './Request';
|
||||||
import TransactionIndex from './TransactionIndex';
|
import TransactionIndex from './TransactionIndex';
|
||||||
import { SigningRequest } from '@polkadot/extension-base/background/types';
|
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<{}> {
|
export default function Signing (): React.ReactElement<{}> {
|
||||||
const requests = useContext(SigningContext);
|
const requests = useContext(SigningReqContext);
|
||||||
const [requestIndex, setRequestIndex] = useState(0);
|
const [requestIndex, setRequestIndex] = useState(0);
|
||||||
|
const isTransaction = useMemo(
|
||||||
|
() => isInnerTransaction(requests[requestIndex]),
|
||||||
|
[requests, requestIndex]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (requestIndex >= requests.length) {
|
if (requestIndex >= requests.length) {
|
||||||
@ -19,13 +27,9 @@ export default function Signing (): React.ReactElement<{}> {
|
|||||||
}
|
}
|
||||||
}, [requests]);
|
}, [requests]);
|
||||||
|
|
||||||
function isTransaction (signingRequest: SigningRequest): boolean {
|
|
||||||
return 'blockNumber' in signingRequest.request.inner;
|
|
||||||
}
|
|
||||||
|
|
||||||
return requests[requestIndex] ? (
|
return requests[requestIndex] ? (
|
||||||
<>
|
<>
|
||||||
<Header text={isTransaction(requests[requestIndex]) ? 'Transaction' : 'Sign message'}>
|
<Header text={isTransaction ? 'Transaction' : 'Sign message'}>
|
||||||
{requests.length > 1 && (
|
{requests.length > 1 && (
|
||||||
<TransactionIndex
|
<TransactionIndex
|
||||||
index={requestIndex}
|
index={requestIndex}
|
||||||
@ -41,7 +45,7 @@ export default function Signing (): React.ReactElement<{}> {
|
|||||||
request={requests[requestIndex].request}
|
request={requests[requestIndex].request}
|
||||||
signId={requests[requestIndex].id}
|
signId={requests[requestIndex].id}
|
||||||
url={requests[requestIndex].url}
|
url={requests[requestIndex].url}
|
||||||
buttonText={isTransaction(requests[requestIndex]) ? 'Sign the transaction' : 'Sign the message'}
|
buttonText={isTransaction ? 'Sign the transaction' : 'Sign the message'}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : <Loading />;
|
) : <Loading />;
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
// This software may be modified and distributed under the terms
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// 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 React, { useEffect, useState } from 'react';
|
||||||
import { Route, Switch } from 'react-router';
|
import { Route, Switch } from 'react-router';
|
||||||
@ -10,8 +10,8 @@ import settings from '@polkadot/ui-settings';
|
|||||||
import { setSS58Format } from '@polkadot/util-crypto';
|
import { setSS58Format } from '@polkadot/util-crypto';
|
||||||
|
|
||||||
import { Loading } from '../components';
|
import { Loading } from '../components';
|
||||||
import { AccountContext, ActionContext, AuthorizeContext, MediaContext, SigningContext } from '../components/contexts';
|
import { AccountContext, ActionContext, AuthorizeReqContext, MediaContext, MetadataReqContext, SigningReqContext } from '../components/contexts';
|
||||||
import { subscribeAccounts, subscribeAuthorize, subscribeSigning } from '../messaging';
|
import { subscribeAccounts, subscribeAuthorizeRequests, subscribeMetadataRequests, subscribeSigningRequests } from '../messaging';
|
||||||
import Accounts from './Accounts';
|
import Accounts from './Accounts';
|
||||||
import Authorize from './Authorize';
|
import Authorize from './Authorize';
|
||||||
import CreateAccount from './CreateAccount';
|
import CreateAccount from './CreateAccount';
|
||||||
@ -19,6 +19,7 @@ import Export from './Export';
|
|||||||
import Forget from './Forget';
|
import Forget from './Forget';
|
||||||
import ImportQr from './ImportQr';
|
import ImportQr from './ImportQr';
|
||||||
import ImportSeed from './ImportSeed';
|
import ImportSeed from './ImportSeed';
|
||||||
|
import Metadata from './Metadata';
|
||||||
import Signing from './Signing';
|
import Signing from './Signing';
|
||||||
import Welcome from './Welcome';
|
import Welcome from './Welcome';
|
||||||
|
|
||||||
@ -51,6 +52,7 @@ export default function Popup (): React.ReactElement<{}> {
|
|||||||
const [authRequests, setAuthRequests] = useState<null | AuthorizeRequest[]>(null);
|
const [authRequests, setAuthRequests] = useState<null | AuthorizeRequest[]>(null);
|
||||||
const [cameraOn, setCameraOn] = useState(settings.get().camera === 'on');
|
const [cameraOn, setCameraOn] = useState(settings.get().camera === 'on');
|
||||||
const [mediaAllowed, setMediaAllowed] = useState(false);
|
const [mediaAllowed, setMediaAllowed] = useState(false);
|
||||||
|
const [metaRequests, setMetaRequests] = useState<null | MetadataRequest[]>(null);
|
||||||
const [signRequests, setSignRequests] = useState<null | SigningRequest[]>(null);
|
const [signRequests, setSignRequests] = useState<null | SigningRequest[]>(null);
|
||||||
const [isWelcomeDone, setWelcomeDone] = useState(false);
|
const [isWelcomeDone, setWelcomeDone] = useState(false);
|
||||||
|
|
||||||
@ -65,8 +67,9 @@ export default function Popup (): React.ReactElement<{}> {
|
|||||||
useEffect((): void => {
|
useEffect((): void => {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
subscribeAccounts(setAccounts),
|
subscribeAccounts(setAccounts),
|
||||||
subscribeAuthorize(setAuthRequests),
|
subscribeAuthorizeRequests(setAuthRequests),
|
||||||
subscribeSigning(setSignRequests)
|
subscribeMetadataRequests(setMetaRequests),
|
||||||
|
subscribeSigningRequests(setSignRequests)
|
||||||
]).catch((error: Error) => console.error(error));
|
]).catch((error: Error) => console.error(error));
|
||||||
|
|
||||||
settings.on('change', ({ camera }): void => setCameraOn(camera === 'on'));
|
settings.on('change', ({ camera }): void => setCameraOn(camera === 'on'));
|
||||||
@ -81,29 +84,33 @@ export default function Popup (): React.ReactElement<{}> {
|
|||||||
const Root = isWelcomeDone
|
const Root = isWelcomeDone
|
||||||
? authRequests && authRequests.length
|
? authRequests && authRequests.length
|
||||||
? Authorize
|
? Authorize
|
||||||
: signRequests && signRequests.length
|
: metaRequests && metaRequests.length
|
||||||
? Signing
|
? Metadata
|
||||||
: Accounts
|
: signRequests && signRequests.length
|
||||||
|
? Signing
|
||||||
|
: Accounts
|
||||||
: Welcome;
|
: Welcome;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Loading>{accounts && authRequests && signRequests && (
|
<Loading>{accounts && authRequests && metaRequests && signRequests && (
|
||||||
<ActionContext.Provider value={_onAction}>
|
<ActionContext.Provider value={_onAction}>
|
||||||
<AccountContext.Provider value={accounts}>
|
<AccountContext.Provider value={accounts}>
|
||||||
<AuthorizeContext.Provider value={authRequests}>
|
<AuthorizeReqContext.Provider value={authRequests}>
|
||||||
<MediaContext.Provider value={cameraOn && mediaAllowed}>
|
<MediaContext.Provider value={cameraOn && mediaAllowed}>
|
||||||
<SigningContext.Provider value={signRequests}>
|
<MetadataReqContext.Provider value={metaRequests}>
|
||||||
<Switch>
|
<SigningReqContext.Provider value={signRequests}>
|
||||||
<Route path='/account/create' component={CreateAccount} />
|
<Switch>
|
||||||
<Route path='/account/forget/:address' component={Forget} />
|
<Route path='/account/create' component={CreateAccount} />
|
||||||
<Route path='/account/export/:address' component={Export} />
|
<Route path='/account/forget/:address' component={Forget} />
|
||||||
<Route path='/account/import-qr' component={ImportQr} />
|
<Route path='/account/export/:address' component={Export} />
|
||||||
<Route path='/account/import-seed' component={ImportSeed} />
|
<Route path='/account/import-qr' component={ImportQr} />
|
||||||
<Route exact path='/' component={Root} />
|
<Route path='/account/import-seed' component={ImportSeed} />
|
||||||
</Switch>
|
<Route exact path='/' component={Root} />
|
||||||
</SigningContext.Provider>
|
</Switch>
|
||||||
|
</SigningReqContext.Provider>
|
||||||
|
</MetadataReqContext.Provider>
|
||||||
</MediaContext.Provider>
|
</MediaContext.Provider>
|
||||||
</AuthorizeContext.Provider>
|
</AuthorizeReqContext.Provider>
|
||||||
</AccountContext.Provider>
|
</AccountContext.Provider>
|
||||||
</ActionContext.Provider>
|
</ActionContext.Provider>
|
||||||
)}</Loading>
|
)}</Loading>
|
||||||
|
|||||||
@ -7,16 +7,17 @@ import { Chain } from '@polkadot/extension-chains/types';
|
|||||||
|
|
||||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import findChain from '@polkadot/extension-chains';
|
|
||||||
import settings from '@polkadot/ui-settings';
|
import settings from '@polkadot/ui-settings';
|
||||||
import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';
|
import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';
|
||||||
|
|
||||||
import { AccountContext } from './contexts';
|
import { AccountContext } from './contexts';
|
||||||
import Identicon from '@polkadot/extension-ui/components/Identicon';
|
import Identicon from './Identicon';
|
||||||
import Svg from '@polkadot/extension-ui/components/Svg';
|
import Svg from './Svg';
|
||||||
import Menu from '@polkadot/extension-ui/components/Menu';
|
import Menu from './Menu';
|
||||||
|
|
||||||
import DetailsImg from '../assets/details.svg';
|
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 {
|
interface Props {
|
||||||
address?: string | null;
|
address?: string | null;
|
||||||
@ -29,7 +30,6 @@ interface Props {
|
|||||||
|
|
||||||
interface Recoded {
|
interface Recoded {
|
||||||
account: AccountJson | null;
|
account: AccountJson | null;
|
||||||
chain: Chain | null;
|
|
||||||
formatted: string | null;
|
formatted: string | null;
|
||||||
prefix: number;
|
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
|
// 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
|
// decode and create a shortcut for the encoded address
|
||||||
const publicKey = decodeAddress(address);
|
const publicKey = decodeAddress(address);
|
||||||
|
|
||||||
// find our account using the actual publicKey, and then find the associated chain
|
// find our account using the actual publicKey, and then find the associated chain
|
||||||
const account = findAccount(accounts, publicKey);
|
const account = findAccount(accounts, publicKey);
|
||||||
const chain = findChain(account?.genesisHash || genesisHash);
|
const prefix = settings.prefix === -1 ? (chain?.ss58Format || 42) : settings.prefix;
|
||||||
const prefix = settings.prefix === -1 ? chain.ss58Format : settings.prefix;
|
|
||||||
|
|
||||||
// always allow the actual settings to override the display
|
// always allow the actual settings to override the display
|
||||||
return {
|
return {
|
||||||
account,
|
account,
|
||||||
chain,
|
|
||||||
formatted: encodeAddress(publicKey, prefix),
|
formatted: encodeAddress(publicKey, prefix),
|
||||||
prefix
|
prefix
|
||||||
};
|
};
|
||||||
@ -66,7 +64,8 @@ const ACCOUNTS_SCREEN_HEIGHT = 500;
|
|||||||
|
|
||||||
function Address ({ address, className, children, genesisHash, name, actions }: Props): React.ReactElement<Props> {
|
function Address ({ address, className, children, genesisHash, name, actions }: Props): React.ReactElement<Props> {
|
||||||
const accounts = useContext(AccountContext);
|
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 [showActionsMenu, setShowActionsMenu] = useState(false);
|
||||||
const [moveMenuUp, setIsMovedMenu] = useState(false);
|
const [moveMenuUp, setIsMovedMenu] = useState(false);
|
||||||
const actionsRef = useRef<HTMLDivElement>(null);
|
const actionsRef = useRef<HTMLDivElement>(null);
|
||||||
@ -75,9 +74,9 @@ function Address ({ address, className, children, genesisHash, name, actions }:
|
|||||||
|
|
||||||
useEffect((): void => {
|
useEffect((): void => {
|
||||||
address && setRecoded(
|
address && setRecoded(
|
||||||
recodeAddress(address, accounts, genesisHash)
|
recodeAddress(address, accounts, chain)
|
||||||
);
|
);
|
||||||
}, [accounts, address, genesisHash]);
|
}, [accounts, address, chain]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showActionsMenu) {
|
if (!showActionsMenu) {
|
||||||
@ -183,7 +182,7 @@ const Settings = styled.div`
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: "";
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 25%;
|
top: 25%;
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import styled from 'styled-components';
|
|||||||
import ArrowLeftImage from '../assets/arrowLeft.svg';
|
import ArrowLeftImage from '../assets/arrowLeft.svg';
|
||||||
import logo from '../assets/pjs.svg';
|
import logo from '../assets/pjs.svg';
|
||||||
import gear from '../assets/gear.svg';
|
import gear from '../assets/gear.svg';
|
||||||
import { useOutsideClick } from '../hooks';
|
import useOutsideClick from '../hooks/useOutsideClick';
|
||||||
import Link from './Link';
|
import Link from './Link';
|
||||||
import Settings from './Settings';
|
import Settings from './Settings';
|
||||||
import Svg from './Svg';
|
import Svg from './Svg';
|
||||||
|
|||||||
@ -39,7 +39,7 @@ const Slider = styled.span`
|
|||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: "";
|
content: '';
|
||||||
height: 16px;
|
height: 16px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
left: 4px;
|
left: 4px;
|
||||||
@ -56,7 +56,7 @@ const Checkbox = styled.input.attrs(() => ({
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
|
||||||
&:checked + ${Slider}:before {
|
&:checked + ${Slider}:before {
|
||||||
transform: translateX(24px);
|
transform: translateX(24px);
|
||||||
}
|
}
|
||||||
@ -70,7 +70,7 @@ export default styled(Switch)`
|
|||||||
height: 24px;
|
height: 24px;
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// 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 React from 'react';
|
||||||
import { AvailableThemes } from '.';
|
import { AvailableThemes } from '.';
|
||||||
@ -12,16 +12,18 @@ const noop = (): void => undefined;
|
|||||||
|
|
||||||
const AccountContext = React.createContext<AccountJson[]>([]);
|
const AccountContext = React.createContext<AccountJson[]>([]);
|
||||||
const ActionContext = React.createContext<(to?: string) => void>(noop);
|
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 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);
|
const ThemeSwitchContext = React.createContext<(theme: AvailableThemes) => void>(noop);
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AccountContext,
|
AccountContext,
|
||||||
ActionContext,
|
ActionContext,
|
||||||
AuthorizeContext,
|
AuthorizeReqContext,
|
||||||
MediaContext,
|
MediaContext,
|
||||||
SigningContext,
|
MetadataReqContext,
|
||||||
|
SigningReqContext,
|
||||||
ThemeSwitchContext
|
ThemeSwitchContext
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,6 +24,7 @@ export { default as Menu } from './Menu';
|
|||||||
export { default as MnemonicSeed } from './MnemonicSeed';
|
export { default as MnemonicSeed } from './MnemonicSeed';
|
||||||
export { default as Settings } from './Settings';
|
export { default as Settings } from './Settings';
|
||||||
export { default as Svg } from './Svg';
|
export { default as Svg } from './Svg';
|
||||||
|
export { default as Table } from './Table';
|
||||||
export { default as TextAreaWithLabel } from './TextAreaWithLabel';
|
export { default as TextAreaWithLabel } from './TextAreaWithLabel';
|
||||||
export { default as Title } from './Title';
|
export { default as Title } from './Title';
|
||||||
export { default as VerticalSpace } from './VerticalSpace';
|
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';
|
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 => {
|
const handleClick = (e: MouseEvent): void => {
|
||||||
if (ref.current && !ref.current.contains(e.target as HTMLInputElement)) {
|
if (ref.current && !ref.current.contains(e.target as HTMLInputElement)) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('click', handleClick);
|
document.addEventListener('click', handleClick);
|
||||||
|
|
||||||
return (): void => {
|
return (): void => {
|
||||||
document.removeEventListener('click', handleClick);
|
document.removeEventListener('click', handleClick);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
@ -2,11 +2,13 @@
|
|||||||
// This software may be modified and distributed under the terms
|
// This software may be modified and distributed under the terms
|
||||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
// 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 { KeypairType } from '@polkadot/util-crypto/types';
|
||||||
|
|
||||||
import extension from 'extensionizer';
|
import extension from 'extensionizer';
|
||||||
import { PORT_EXTENSION } from '@polkadot/extension-base/defaults';
|
import { PORT_EXTENSION } from '@polkadot/extension-base/defaults';
|
||||||
|
import { findChain } from '@polkadot/extension-chains';
|
||||||
|
|
||||||
interface Handler {
|
interface Handler {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ -27,7 +29,7 @@ port.onMessage.addListener((data): void => {
|
|||||||
const handler = handlers[data.id];
|
const handler = handlers[data.id];
|
||||||
|
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
console.error(`Uknown response: ${JSON.stringify(data)}`);
|
console.error(`Unknown response: ${JSON.stringify(data)}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,14 +72,14 @@ export async function forgetAccount (address: string): Promise<boolean> {
|
|||||||
return sendMessage('pri(accounts.forget)', { address });
|
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> {
|
export async function approveAuthRequest (id: string): Promise<boolean> {
|
||||||
return sendMessage('pri(authorize.approve)', { id });
|
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> {
|
export async function cancelSignRequest (id: string): Promise<boolean> {
|
||||||
return sendMessage('pri(signing.cancel)', { id });
|
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 });
|
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> {
|
export async function subscribeAccounts (cb: (accounts: AccountJson[]) => void): Promise<boolean> {
|
||||||
return sendMessage('pri(accounts.subscribe)', null, cb);
|
return sendMessage('pri(accounts.subscribe)', null, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function subscribeAuthorize (cb: (accounts: AuthorizeRequest[]) => void): Promise<boolean> {
|
export async function subscribeAuthorizeRequests (cb: (accounts: AuthorizeRequest[]) => void): Promise<boolean> {
|
||||||
return sendMessage('pri(authorize.subscribe)', null, cb);
|
return sendMessage('pri(authorize.requests)', null, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function subscribeSigning (cb: (accounts: SigningRequest[]) => void): Promise<boolean> {
|
export async function subscribeMetadataRequests (cb: (accounts: MetadataRequest[]) => void): Promise<boolean> {
|
||||||
return sendMessage('pri(signing.subscribe)', null, cb);
|
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 }> {
|
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"
|
resolution: "@polkadot/extension-chains@workspace:packages/extension-chains"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime": ^7.9.2
|
"@babel/runtime": ^7.9.2
|
||||||
edgeware-node-types: ^1.2.0
|
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@polkadot/api": "*"
|
"@polkadot/api": "*"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
@ -8271,13 +8270,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"ee-first@npm:1.1.1":
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
resolution: "ee-first@npm:1.1.1"
|
resolution: "ee-first@npm:1.1.1"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user