Skip to content

Commit

Permalink
feat: add isConnected method and revoke event (#641)
Browse files Browse the repository at this point in the history
  • Loading branch information
emccorson committed Feb 29, 2024
1 parent 2adccfb commit 1ca4f13
Show file tree
Hide file tree
Showing 15 changed files with 128 additions and 18 deletions.
14 changes: 14 additions & 0 deletions apps/extension/src/background/approvals/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
ApproveConnectInterfaceMsg,
ApproveSignArbitraryMsg,
ApproveTxMsg,
IsConnectionApprovedMsg,
} from "provider";
import { Env, Handler, InternalHandler, Message } from "router";
import {
Expand All @@ -26,6 +27,11 @@ export const getHandler: (service: ApprovalsService) => Handler = (service) => {
env,
msg as SubmitApprovedTxMsg
);
case IsConnectionApprovedMsg:
return handleIsConnectionApprovedMsg(service)(
env,
msg as IsConnectionApprovedMsg
);
case ApproveConnectInterfaceMsg:
return handleApproveConnectInterfaceMsg(service)(
env,
Expand Down Expand Up @@ -87,6 +93,14 @@ const handleSubmitApprovedTxMsg: (
};
};

const handleIsConnectionApprovedMsg: (
service: ApprovalsService
) => InternalHandler<IsConnectionApprovedMsg> = (service) => {
return async (_, { origin }) => {
return await service.isConnectionApproved(origin);
};
};

const handleApproveConnectInterfaceMsg: (
service: ApprovalsService
) => InternalHandler<ApproveConnectInterfaceMsg> = (service) => {
Expand Down
2 changes: 2 additions & 0 deletions apps/extension/src/background/approvals/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
ApproveConnectInterfaceMsg,
ApproveSignArbitraryMsg,
ApproveTxMsg,
IsConnectionApprovedMsg,
} from "provider";
import { Router } from "router";
import {
Expand All @@ -24,6 +25,7 @@ export function init(router: Router, service: ApprovalsService): void {
router.registerMessage(ApproveSignArbitraryMsg);
router.registerMessage(RejectSignatureMsg);
router.registerMessage(SubmitApprovedSignatureMsg);
router.registerMessage(IsConnectionApprovedMsg);
router.registerMessage(ApproveConnectInterfaceMsg);
router.registerMessage(ConnectInterfaceResponseMsg);
router.registerMessage(RevokeConnectionMsg);
Expand Down
6 changes: 5 additions & 1 deletion apps/extension/src/background/approvals/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { KeyRingService, TabStore } from "background/keyring";
import { LedgerService } from "background/ledger";
import { VaultService } from "background/vault";
import BigNumber from "bignumber.js";
import { ExtensionBroadcaster } from "extension";
import createMockInstance from "jest-create-mock-instance";
import { KVStoreMock } from "test/init";
import { ApprovalsService } from "./service";
Expand Down Expand Up @@ -55,6 +56,8 @@ describe.only("approvals service", () => {
const vaultService: jest.Mocked<VaultService> = createMockInstance(
VaultService as any
);
const broadcaster: jest.Mocked<ExtensionBroadcaster> =
createMockInstance(ExtensionBroadcaster);

service = new ApprovalsService(
txStore,
Expand All @@ -63,7 +66,8 @@ describe.only("approvals service", () => {
approvedOriginsStore,
keyRingService,
ledgerService,
vaultService
vaultService,
broadcaster
);
});

Expand Down
23 changes: 17 additions & 6 deletions apps/extension/src/background/approvals/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import {
import { assertNever, paramsToUrl } from "@namada/utils";
import { KeyRingService, TabStore } from "background/keyring";
import { LedgerService } from "background/ledger";

import { VaultService } from "background/vault";
import { ExtensionBroadcaster } from "extension";
import { ApprovedOriginsStore, TxStore } from "./types";
import {
APPROVED_ORIGINS_KEY,
Expand Down Expand Up @@ -52,7 +52,8 @@ export class ApprovalsService {
protected readonly approvedOriginsStore: KVStore<ApprovedOriginsStore>,
protected readonly keyRingService: KeyRingService,
protected readonly ledgerService: LedgerService,
protected readonly vaultService: VaultService
protected readonly vaultService: VaultService,
protected readonly broadcaster: ExtensionBroadcaster
) {}

async approveSignature(
Expand Down Expand Up @@ -344,6 +345,13 @@ export class ApprovalsService {
return await this._clearPendingTx(msgId);
}

async isConnectionApproved(interfaceOrigin: string): Promise<boolean> {
const approvedOrigins =
(await this.approvedOriginsStore.get(APPROVED_ORIGINS_KEY)) || [];

return approvedOrigins.includes(interfaceOrigin);
}

async approveConnection(
interfaceTabId: number,
interfaceOrigin: string
Expand All @@ -357,10 +365,9 @@ export class ApprovalsService {
interfaceOrigin,
});

const approvedOrigins =
(await this.approvedOriginsStore.get(APPROVED_ORIGINS_KEY)) || [];
const alreadyApproved = await this.isConnectionApproved(interfaceOrigin);

if (!approvedOrigins.includes(interfaceOrigin)) {
if (!alreadyApproved) {
const approvalWindow = await this._launchApprovalWindow(url);
const popupTabId = approvalWindow.tabs?.[0]?.id;

Expand All @@ -376,6 +383,9 @@ export class ApprovalsService {
this.resolverMap[popupTabId] = { resolve, reject };
});
}

// A resolved promise is implicitly returned here if the origin had
// previously been approved.
}

async approveConnectionResponse(
Expand Down Expand Up @@ -403,7 +413,8 @@ export class ApprovalsService {
}

async revokeConnection(originToRevoke: string): Promise<void> {
return removeApprovedOrigin(this.approvedOriginsStore, originToRevoke);
await removeApprovedOrigin(this.approvedOriginsStore, originToRevoke);
await this.broadcaster.revokeConnection();
}

private async _clearPendingTx(msgId: string): Promise<void> {
Expand Down
3 changes: 2 additions & 1 deletion apps/extension/src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ const init = new Promise<void>(async (resolve) => {
approvedOriginsStore,
keyRingService,
ledgerService,
vaultService
vaultService,
broadcaster
);

// Initialize messages and handlers
Expand Down
24 changes: 24 additions & 0 deletions apps/extension/src/content/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,26 @@ export class VaultLockedEventMsg extends Message<void> {
}
}

export class ConnectionRevokedEventMsg extends Message<void> {
public static type(): Events {
return Events.ConnectionRevoked;
}

constructor() {
super();
}

validate(): void {}

route(): string {
return Routes.InteractionForeground;
}

type(): string {
return ConnectionRevokedEventMsg.type();
}
}

export function initEvents(router: Router): void {
router.registerMessage(AccountChangedEventMsg);
router.registerMessage(NetworkChangedEventMsg);
Expand All @@ -210,6 +230,7 @@ export function initEvents(router: Router): void {
router.registerMessage(TxStartedEvent);
router.registerMessage(TxCompletedEvent);
router.registerMessage(VaultLockedEventMsg);
router.registerMessage(ConnectionRevokedEventMsg);

router.addHandler(Routes.InteractionForeground, (_, msg) => {
const clonedMsg =
Expand Down Expand Up @@ -248,6 +269,9 @@ export function initEvents(router: Router): void {
case VaultLockedEventMsg:
window.dispatchEvent(new CustomEvent(Events.ExtensionLocked));
break;
case ConnectionRevokedEventMsg:
window.dispatchEvent(new CustomEvent(Events.ConnectionRevoked));
break;
default:
throw new Error("Unknown msg type");
}
Expand Down
5 changes: 5 additions & 0 deletions apps/extension/src/extension/ExtensionBroadcaster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { KVStore } from "@namada/storage";
import { TabStore, syncTabs } from "background/keyring";
import {
AccountChangedEventMsg,
ConnectionRevokedEventMsg,
NetworkChangedEventMsg,
ProposalsUpdatedEventMsg,
TxCompletedEvent,
Expand Down Expand Up @@ -59,6 +60,10 @@ export class ExtensionBroadcaster {
await this.sendMsgToTabs(new VaultLockedEventMsg());
}

async revokeConnection(): Promise<void> {
await this.sendMsgToTabs(new ConnectionRevokedEventMsg());
}

/**
* Query all existing tabs, and send provided message to each
*/
Expand Down
6 changes: 5 additions & 1 deletion apps/extension/src/provider/InjectedNamada.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ import { InjectedProxy } from "./InjectedProxy";
import { Signer } from "./Signer";

export class InjectedNamada implements INamada {
constructor(private readonly _version: string) { }
constructor(private readonly _version: string) {}

public async connect(): Promise<void> {
return await InjectedProxy.requestMethod<string, void>("connect");
}

public async isConnected(): Promise<boolean> {
return await InjectedProxy.requestMethod<string, boolean>("isConnected");
}

public async accounts(): Promise<DerivedAccount[]> {
return await InjectedProxy.requestMethod<string, DerivedAccount[]>(
"accounts"
Expand Down
14 changes: 13 additions & 1 deletion apps/extension/src/provider/Namada.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
FetchAndStoreMaspParamsMsg,
GetChainMsg,
HasMaspParamsMsg,
IsConnectionApprovedMsg,
QueryAccountsMsg,
QueryBalancesMsg,
QueryDefaultAccountMsg,
Expand All @@ -28,7 +29,7 @@ export class Namada implements INamada {
constructor(
private readonly _version: string,
protected readonly requester?: MessageRequester
) { }
) {}

public async connect(): Promise<void> {
return await this.requester?.sendMessage(
Expand All @@ -37,6 +38,17 @@ export class Namada implements INamada {
);
}

public async isConnected(): Promise<boolean> {
if (!this.requester) {
throw new Error("no requester");
}

return await this.requester.sendMessage(
Ports.Background,
new IsConnectionApprovedMsg()
);
}

public async accounts(
// TODO: This argument should be removed in the future!
_chainId?: string
Expand Down
12 changes: 8 additions & 4 deletions apps/extension/src/provider/Proxy.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { KVStore } from "@namada/storage";
import { ApprovedOriginsStore, APPROVED_ORIGINS_KEY } from "background/approvals";
import {
APPROVED_ORIGINS_KEY,
ApprovedOriginsStore,
} from "background/approvals";

import { Namada } from "./Namada";
import { ProxyRequest, ProxyRequestResponse, ProxyRequestTypes } from "./types";

export class Proxy {
static start(
namada: Namada,
approvedOriginsStore: KVStore<ApprovedOriginsStore>,
approvedOriginsStore: KVStore<ApprovedOriginsStore>
): void {
Proxy.addMessageListener(async (e) => {
const message = e.data;
Expand All @@ -18,8 +21,9 @@ export class Proxy {

const { method, args } = message;

if (method !== "connect") {
const approvedOrigins = await approvedOriginsStore.get(APPROVED_ORIGINS_KEY) || [];
if (method !== "connect" && method !== "isConnected") {
const approvedOrigins =
(await approvedOriginsStore.get(APPROVED_ORIGINS_KEY)) || [];
if (!approvedOrigins.includes(e.origin)) {
return;
}
Expand Down
24 changes: 23 additions & 1 deletion apps/extension/src/provider/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ enum Route {
}

enum MessageType {
IsConnectionApproved = "is-connection-approved",
ApproveConnectInterface = "approve-connect-interface",
QueryAccounts = "query-accounts",
QueryDefaultAccount = "query-default-account",
Expand All @@ -42,6 +43,27 @@ enum MessageType {
/**
* Messages routed from providers to Chains service
*/
export class IsConnectionApprovedMsg extends Message<boolean> {
public static type(): MessageType {
return MessageType.IsConnectionApproved;
}

constructor() {
super();
}

validate(): void {
return;
}

route(): string {
return Route.Approvals;
}

type(): string {
return IsConnectionApprovedMsg.type();
}
}

export class ApproveConnectInterfaceMsg extends Message<void> {
public static type(): MessageType {
Expand Down Expand Up @@ -74,7 +96,7 @@ export class GetChainMsg extends Message<Chain> {
super();
}

validate(): void { }
validate(): void {}

route(): string {
return Route.Chains;
Expand Down
5 changes: 3 additions & 2 deletions apps/extension/src/test/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const cryptoMemory = require("@namada/crypto").__wasm.memory;
export class KVStoreMock<T> implements KVStore<T> {
private storage: { [key: string]: T | null } = {};

constructor(readonly _prefix: string) { }
constructor(readonly _prefix: string) {}

get<U extends T>(key: string): Promise<U | undefined> {
return new Promise((resolve) => {
Expand Down Expand Up @@ -141,7 +141,8 @@ export const init = async (): Promise<{
approvedOriginsStore,
keyRingService,
ledgerService,
vaultService
vaultService,
broadcaster
);

const init = new Promise<void>(async (resolve) => {
Expand Down
6 changes: 5 additions & 1 deletion packages/integrations/src/Namada.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { BridgeProps, Integration } from "./types/Integration";
export default class Namada implements Integration<Account, Signer> {
private _namada: WindowWithNamada["namada"] | undefined;

constructor(public readonly chain: Chain) { }
constructor(public readonly chain: Chain) {}

public get instance(): INamada | undefined {
return this._namada;
Expand All @@ -35,6 +35,10 @@ export default class Namada implements Integration<Account, Signer> {
await this._namada?.connect(chainId);
}

public async isConnected(): Promise<boolean | undefined> {
return await this._namada?.isConnected();
}

public async getChain(): Promise<Chain | undefined> {
return await this._namada?.getChain();
}
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum Events {
UpdatedStaking = "namada-updated-staking",
ProposalsUpdated = "namada-proposals-updated",
ExtensionLocked = "namada-extension-locked",
ConnectionRevoked = "namada-connection-revoked",
}

// Keplr extension events
Expand Down
Loading

0 comments on commit 1ca4f13

Please sign in to comment.