ghost-keychain/packages/extension-base/src/background/handlers/Tabs.ts
Jaco Greeff dec07f6e96
Bump dev (import sorting) (#564)
* Bump dev (import sorting)

* Bump dev
2020-12-08 11:20:57 +01:00

211 lines
7.3 KiB
TypeScript

// Copyright 2019-2020 @polkadot/extension authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { InjectedAccount, InjectedMetadataKnown, MetadataDef, ProviderMeta } from '@polkadot/extension-inject/types';
import type { KeyringPair } from '@polkadot/keyring/types';
import type { JsonRpcResponse } from '@polkadot/rpc-provider/types';
import type { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
import type { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import type { MessageTypes, RequestAuthorizeTab, RequestRpcSend, RequestRpcSubscribe, RequestRpcUnsubscribe, RequestTypes, ResponseRpcListProviders, ResponseSigning, ResponseTypes, SubscriptionMessageTypes } from '../types';
import { PHISHING_PAGE_REDIRECT } from '@polkadot/extension-base/defaults';
import { checkIfDenied } from '@polkadot/phishing';
import keyring from '@polkadot/ui-keyring';
import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
import { assert } from '@polkadot/util';
import RequestBytesSign from '../RequestBytesSign';
import RequestExtrinsicSign from '../RequestExtrinsicSign';
import State from './State';
import { createSubscription, unsubscribe } from './subscriptions';
function transformAccounts (accounts: SubjectInfo): InjectedAccount[] {
return Object
.values(accounts)
.filter(({ json: { meta: { isHidden } } }) => !isHidden)
.sort((a, b) => (a.json.meta.whenCreated || 0) - (b.json.meta.whenCreated || 0))
.map(({ json: { address, meta: { genesisHash, name } } }): InjectedAccount => ({
address, genesisHash, name
}));
}
export default class Tabs {
readonly #state: State;
constructor (state: State) {
this.#state = state;
}
private authorize (url: string, request: RequestAuthorizeTab): Promise<boolean> {
return this.#state.authorizeUrl(url, request);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
private accountsList (url: string): InjectedAccount[] {
return transformAccounts(accountsObservable.subject.getValue());
}
// FIXME This looks very much like what we have in Extension
private accountsSubscribe (url: string, id: string, port: chrome.runtime.Port): boolean {
const cb = createSubscription<'pub(accounts.subscribe)'>(id, port);
const subscription = accountsObservable.subject.subscribe((accounts: SubjectInfo): void =>
cb(transformAccounts(accounts))
);
port.onDisconnect.addListener((): void => {
unsubscribe(id);
subscription.unsubscribe();
});
return true;
}
private getSigningPair (address: string): KeyringPair {
const pair = keyring.getPair(address);
assert(pair, 'Unable to find keypair');
return pair;
}
private bytesSign (url: string, request: SignerPayloadRaw): Promise<ResponseSigning> {
const address = request.address;
const pair = this.getSigningPair(address);
return this.#state.sign(url, new RequestBytesSign(request), { address, ...pair.meta });
}
private extrinsicSign (url: string, request: SignerPayloadJSON): Promise<ResponseSigning> {
const address = request.address;
const pair = this.getSigningPair(address);
return this.#state.sign(url, new RequestExtrinsicSign(request), { address, ...pair.meta });
}
private metadataProvide (url: string, request: MetadataDef): Promise<boolean> {
return this.#state.injectMetadata(url, request);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
private metadataList (url: string): InjectedMetadataKnown[] {
return this.#state.knownMetadata.map(({ genesisHash, specVersion }) => ({
genesisHash,
specVersion
}));
}
private rpcListProviders (): Promise<ResponseRpcListProviders> {
return this.#state.rpcListProviders();
}
private rpcSend (request: RequestRpcSend, port: chrome.runtime.Port): Promise<JsonRpcResponse> {
return this.#state.rpcSend(request, port);
}
private rpcStartProvider (key: string, port: chrome.runtime.Port): Promise<ProviderMeta> {
return this.#state.rpcStartProvider(key, port);
}
private async rpcSubscribe (request: RequestRpcSubscribe, id: string, port: chrome.runtime.Port): Promise<boolean> {
const innerCb = createSubscription<'pub(rpc.subscribe)'>(id, port);
const cb = (_error: Error | null, data: SubscriptionMessageTypes['pub(rpc.subscribe)']): void => innerCb(data);
const subscriptionId = await this.#state.rpcSubscribe(request, cb, port);
port.onDisconnect.addListener((): void => {
unsubscribe(id);
this.rpcUnsubscribe({ ...request, subscriptionId }, port).catch(console.error);
});
return true;
}
private rpcSubscribeConnected (request: null, id: string, port: chrome.runtime.Port): Promise<boolean> {
const innerCb = createSubscription<'pub(rpc.subscribeConnected)'>(id, port);
const cb = (_error: Error | null, data: SubscriptionMessageTypes['pub(rpc.subscribeConnected)']): void => innerCb(data);
this.#state.rpcSubscribeConnected(request, cb, port);
port.onDisconnect.addListener((): void => {
unsubscribe(id);
});
return Promise.resolve(true);
}
private async rpcUnsubscribe (request: RequestRpcUnsubscribe, port: chrome.runtime.Port): Promise<boolean> {
return this.#state.rpcUnsubscribe(request, port);
}
private redirectPhishingLanding () {
const url = `${chrome.extension.getURL('index.html')}#${PHISHING_PAGE_REDIRECT}`;
chrome.tabs.update({ url });
return null;
}
private async redirectIfPhishing (url: string): Promise<boolean> {
const isInDenyList = await checkIfDenied(url);
if (isInDenyList) {
this.redirectPhishingLanding();
}
return false;
}
public async handle<TMessageType extends MessageTypes> (id: string, type: TMessageType, request: RequestTypes[TMessageType], url: string, port: chrome.runtime.Port): Promise<ResponseTypes[keyof ResponseTypes]> {
if (type === 'pub(phishing.redirectIfDenied)') {
return this.redirectIfPhishing(url);
}
if (type !== 'pub(authorize.tab)') {
this.#state.ensureUrlAuthorized(url);
}
switch (type) {
case 'pub(authorize.tab)':
return this.authorize(url, request as RequestAuthorizeTab);
case 'pub(accounts.list)':
return this.accountsList(url);
case 'pub(accounts.subscribe)':
return this.accountsSubscribe(url, id, port);
case 'pub(bytes.sign)':
return this.bytesSign(url, request as SignerPayloadRaw);
case 'pub(extrinsic.sign)':
return this.extrinsicSign(url, request as SignerPayloadJSON);
case 'pub(metadata.list)':
return this.metadataList(url);
case 'pub(metadata.provide)':
return this.metadataProvide(url, request as MetadataDef);
case 'pub(rpc.listProviders)':
return this.rpcListProviders();
case 'pub(rpc.send)':
return this.rpcSend(request as RequestRpcSend, port);
case 'pub(rpc.startProvider)':
return this.rpcStartProvider(request as string, port);
case 'pub(rpc.subscribe)':
return this.rpcSubscribe(request as RequestRpcSubscribe, id, port);
case 'pub(rpc.subscribeConnected)':
return this.rpcSubscribeConnected(request as null, id, port);
case 'pub(rpc.unsubscribe)':
return this.rpcUnsubscribe(request as RequestRpcUnsubscribe, port);
default:
throw new Error(`Unable to handle message of type ${type}`);
}
}
}