diff --git a/CHANGELOG.md b/CHANGELOG.md index 89531924..bb223d7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # CHANGELOG +## master + +Contributed: + +- Account selection/management for authorized sites (Thanks to https://github.com/Tbaut) +- UI icon & cursor adjustments for authorization (Thanks to https://github.com/roiLeo) + +Changes: + +- Allow for unsubscribe handling on account subscriptions +- Add error handling for user-supplied account callbacks + + ## 0.44.1 Jun 6, 2022 **Important** CHANGELOG entries are a rollup of details since last publish to the stores diff --git a/packages/extension-base/src/background/handlers/Tabs.ts b/packages/extension-base/src/background/handlers/Tabs.ts index 4932ba59..3020002f 100644 --- a/packages/extension-base/src/background/handlers/Tabs.ts +++ b/packages/extension-base/src/background/handlers/Tabs.ts @@ -1,12 +1,13 @@ // Copyright 2019-2022 @polkadot/extension authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { Subscription } from 'rxjs'; 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, RequestAccountList, RequestAuthorizeTab, RequestRpcSend, RequestRpcSubscribe, RequestRpcUnsubscribe, RequestTypes, ResponseRpcListProviders, ResponseSigning, ResponseTypes, SubscriptionMessageTypes } from '../types'; +import type { MessageTypes, RequestAccountList, RequestAccountUnsubscribe, RequestAuthorizeTab, RequestRpcSend, RequestRpcSubscribe, RequestRpcUnsubscribe, RequestTypes, ResponseRpcListProviders, ResponseSigning, ResponseTypes, SubscriptionMessageTypes } from '../types'; import { PHISHING_PAGE_REDIRECT } from '@polkadot/extension-base/defaults'; import { canDerive } from '@polkadot/extension-base/utils'; @@ -21,6 +22,11 @@ import { withErrorLog } from './helpers'; import State, { AuthResponse } from './State'; import { createSubscription, unsubscribe } from './subscriptions'; +interface AccountSub { + subscription: Subscription; + url: string; +} + function transformAccounts (accounts: SubjectInfo, anyType = false): InjectedAccount[] { return Object .values(accounts) @@ -36,6 +42,8 @@ function transformAccounts (accounts: SubjectInfo, anyType = false): InjectedAcc } export default class Tabs { + readonly #accountSubs: Record = {}; + readonly #state: State; constructor (state: State) { @@ -65,21 +73,37 @@ export default class Tabs { return this.filterForAuthorizedAccounts(transformedAccounts, url); } - private accountsSubscribeAuthorized (url: string, id: string, port: chrome.runtime.Port): boolean { - const cb = createSubscription<'pub(accounts.subscribeAuthorized)'>(id, port); - const subscription = accountsObservable.subject.subscribe((accounts: SubjectInfo): void => { - const transformedAccounts = transformAccounts(accounts); + private accountsSubscribeAuthorized (url: string, id: string, port: chrome.runtime.Port): string { + const cb = createSubscription<'pub(accounts.subscribe)'>(id, port); - return cb( - this.filterForAuthorizedAccounts(transformedAccounts, url) - ); - }); + this.#accountSubs[id] = { + subscription: accountsObservable.subject.subscribe((accounts: SubjectInfo): void => { + const transformedAccounts = transformAccounts(accounts); + + cb(this.filterForAuthorizedAccounts(transformedAccounts, url)); + }), + url + }; port.onDisconnect.addListener((): void => { - unsubscribe(id); - subscription.unsubscribe(); + this.accountsUnsubscribe(url, { id }); }); + return id; + } + + private accountsUnsubscribe (url: string, { id }: RequestAccountUnsubscribe): boolean { + const sub = this.#accountSubs[id]; + + if (!sub || sub.url !== url) { + return false; + } + + delete this.#accountSubs[id]; + + unsubscribe(id); + sub.subscription.unsubscribe(); + return true; } @@ -199,12 +223,15 @@ export default class Tabs { case 'pub(authorize.tab)': return this.authorize(url, request as RequestAuthorizeTab); - case 'pub(accounts.listAuthorized)': + case 'pub(accounts.list)': return this.accountsListAuthorized(url, request as RequestAccountList); - case 'pub(accounts.subscribeAuthorized)': + case 'pub(accounts.subscribe)': return this.accountsSubscribeAuthorized(url, id, port); + case 'pub(accounts.unsubscribe)': + return this.accountsUnsubscribe(url, request as RequestAccountUnsubscribe); + case 'pub(bytes.sign)': return this.bytesSign(url, request as SignerPayloadRaw); diff --git a/packages/extension-base/src/background/types.ts b/packages/extension-base/src/background/types.ts index 3506836f..e63cc507 100644 --- a/packages/extension-base/src/background/types.ts +++ b/packages/extension-base/src/background/types.ts @@ -116,8 +116,9 @@ export interface RequestSignatures { 'pri(signing.requests)': [RequestSigningSubscribe, boolean, SigningRequest[]]; 'pri(window.open)': [AllowedPath, boolean]; // public/external requests, i.e. from a page - 'pub(accounts.listAuthorized)': [RequestAccountList, InjectedAccount[]]; - 'pub(accounts.subscribeAuthorized)': [RequestAccountSubscribe, boolean, InjectedAccount[]]; + 'pub(accounts.list)': [RequestAccountList, InjectedAccount[]]; + 'pub(accounts.subscribe)': [RequestAccountSubscribe, string, InjectedAccount[]]; + 'pub(accounts.unsubscribe)': [RequestAccountUnsubscribe, boolean]; 'pub(authorize.tab)': [RequestAuthorizeTab, Promise]; 'pub(bytes.sign)': [SignerPayloadRaw, ResponseSigning]; 'pub(extrinsic.sign)': [SignerPayloadJSON, ResponseSigning]; @@ -260,6 +261,10 @@ export interface RequestAccountList { export type RequestAccountSubscribe = null; +export interface RequestAccountUnsubscribe { + id: string; +} + export interface RequestRpcSend { method: string; params: unknown[]; diff --git a/packages/extension-base/src/page/Accounts.ts b/packages/extension-base/src/page/Accounts.ts index b821f9b7..af7e558f 100644 --- a/packages/extension-base/src/page/Accounts.ts +++ b/packages/extension-base/src/page/Accounts.ts @@ -13,15 +13,21 @@ export default class Accounts implements InjectedAccounts { } public get (anyType?: boolean): Promise { - return sendRequest('pub(accounts.listAuthorized)', { anyType }); + return sendRequest('pub(accounts.list)', { anyType }); } public subscribe (cb: (accounts: InjectedAccount[]) => unknown): Unsubcall { - sendRequest('pub(accounts.subscribeAuthorized)', null, cb) - .catch((error: Error) => console.error(error)); + let id: string | null = null; + + sendRequest('pub(accounts.subscribe)', null, cb) + .then((subId): void => { + id = subId; + }) + .catch(console.error); return (): void => { - // FIXME we need the ability to unsubscribe + id && sendRequest('pub(accounts.unsubscribe)', { id }) + .catch(console.error); }; } }