Skip to content

Commit

Permalink
feat(aepp): extract class to connect to wallet from AeSdkAepp
Browse files Browse the repository at this point in the history
  • Loading branch information
davidyuk committed Jul 23, 2023
1 parent 1d84843 commit c3570ac
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 21 deletions.
37 changes: 27 additions & 10 deletions examples/browser/aepp/src/Connect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<script>
import {
walletDetector, BrowserWindowMessageConnection, RpcConnectionDenyError, RpcRejectedByUserError,
WalletConnectorFrame,
} from '@aeternity/aepp-sdk';
import { mapState } from 'vuex';
Expand Down Expand Up @@ -93,6 +94,7 @@ export default {
stopDetection();
resolve(newWallet.getConnection());
this.cancelWalletDetection = null;
this.walletInfo = newWallet.info;
}
});
this.cancelWalletDetection = () => {
Expand All @@ -103,25 +105,40 @@ export default {
};
});
},
async setNode(networkId) {
const [{ name }] = (await this.aeSdk.getNodesInPool())
.filter((node) => node.nodeNetworkId === networkId);
this.aeSdk.selectNode(name);
this.$store.commit('setNetworkId', networkId);
},
setAccount(account) {
if (Object.keys(this.aeSdk.accounts).length) this.aeSdk.removeAccount(this.aeSdk.address);
this.aeSdk.addAccount(account, { select: true });
this.$store.commit('setAddress', account.address);
},
async connect() {
this.walletConnecting = true;
this.aeSdk.onDisconnect = () => {
this.walletConnected = false;
this.walletInfo = null;
this.$store.commit('setAddress', undefined);
if (this.reverseIframe) this.reverseIframe.remove();
};
try {
const connection = await this.detectWallets();
try {
this.walletInfo = await this.aeSdk.connectToWallet(connection);
this.walletConnector = await WalletConnectorFrame.connect('Simple æpp', connection);
} catch (error) {
if (error instanceof RpcConnectionDenyError) connection.disconnect();
throw error;
}
this.walletConnector.on('disconnect', () => {
this.walletConnected = false;
this.walletInfo = null;
this.$store.commit('setAddress', undefined);
if (this.reverseIframe) this.reverseIframe.remove();
});
this.walletConnected = true;
const { address: { current } } = await this.aeSdk.subscribeAddress('subscribe', 'connected');
this.$store.commit('setAddress', Object.keys(current)[0]);
this.setNode(this.walletConnector.networkId);
this.walletConnector.on('networkIdChange', (networkId) => this.setNode(networkId));
this.walletConnector.on('accountsChange', (accounts) => this.setAccount(accounts[0]));
await this.walletConnector.subscribeAccounts('subscribe', 'current');
} catch (error) {
if (
error.message === 'Wallet detection cancelled'
Expand All @@ -134,7 +151,7 @@ export default {
}
},
disconnect() {
this.aeSdk.disconnectWallet();
this.walletConnector.disconnect();
},
},
};
Expand Down
14 changes: 3 additions & 11 deletions examples/browser/aepp/src/store.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { shallowReactive } from 'vue';
import { createStore } from 'vuex';
import { AeSdkAepp, Node, CompilerHttp } from '@aeternity/aepp-sdk';
import { AeSdk, Node, CompilerHttp } from '@aeternity/aepp-sdk';

const TESTNET_NODE_URL = 'https://testnet.aeternity.io';
const MAINNET_NODE_URL = 'https://mainnet.aeternity.io';
Expand All @@ -10,21 +10,13 @@ const store = createStore({
state: {
address: undefined,
networkId: undefined,
// AeSdkAepp instance can't be in deep reactive https://github.com/aeternity/aepp-sdk-js/blob/develop/docs/README.md#vue3
aeSdk: shallowReactive(new AeSdkAepp({
name: 'Simple æpp',
// AeSdk instance can't be in deep reactive https://github.com/aeternity/aepp-sdk-js/blob/develop/docs/README.md#vue3
aeSdk: shallowReactive(new AeSdk({
nodes: [
{ name: 'testnet', instance: new Node(TESTNET_NODE_URL) },
{ name: 'mainnet', instance: new Node(MAINNET_NODE_URL) },
],
onCompiler: new CompilerHttp(COMPILER_URL),
async onNetworkChange({ networkId }) {
const [{ name }] = (await this.getNodesInPool())
.filter((node) => node.nodeNetworkId === networkId);
this.selectNode(name);
store.commit('setNetworkId', networkId);
},
onAddressChange: ({ current }) => store.commit('setAddress', Object.keys(current)[0]),
})),
},
mutations: {
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"bs58": "^5.0.0",
"buffer": "^6.0.3",
"canonicalize": "^2.0.0",
"eventemitter3": "^5.0.1",
"events": "^3.3.0",
"isomorphic-ws": "^5.0.0",
"json-bigint": "^1.0.0",
Expand Down
1 change: 1 addition & 0 deletions src/AeSdkAepp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import BrowserConnection from './aepp-wallet-communication/connection/Browser';
/**
* RPC handler for AEPP side
* Contain functionality for wallet interaction and connect it to sdk
* @deprecated use WalletConnectorFrame instead
* @category aepp wallet communication
*/
export default class AeSdkAepp extends AeSdkBase {
Expand Down
41 changes: 41 additions & 0 deletions src/aepp-wallet-communication/WalletConnectorFrame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Network } from './rpc/types';
import BrowserConnection from './connection/Browser';
import WalletConnectorFrameBase from './WalletConnectorFrameBase';

interface EventsNetworkId {
networkIdChange: (networkId: string) => void;
}

/**
* Connect to wallet as iframe/web-extension
* @category aepp wallet communication
*/
export default class WalletConnectorFrame extends WalletConnectorFrameBase<EventsNetworkId> {
#networkId = '';

/**
* The last network id reported by wallet
*/
get networkId(): string {
return this.#networkId;
}

protected override _updateNetwork(params: Network): void {
this.#networkId = params.networkId;
this.emit('networkIdChange', this.#networkId);
}

/**
* Connect to wallet
* @param name - Aepp name
* @param connection - Wallet connection object
*/
static async connect(
name: string,
connection: BrowserConnection,
): Promise<WalletConnectorFrame> {
const connector = new WalletConnectorFrame();
await WalletConnectorFrame._connect(name, connection, connector, false);
return connector;
}
}
119 changes: 119 additions & 0 deletions src/aepp-wallet-communication/WalletConnectorFrameBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import EventEmitter from 'eventemitter3';
import AccountRpc from '../account/Rpc';
import { Encoded } from '../utils/encoder';
import {
Accounts, RPC_VERSION, Network, WalletApi, AeppApi,
} from './rpc/types';
import RpcClient from './rpc/RpcClient';
import { METHODS, SUBSCRIPTION_TYPES } from './schema';
import { NoWalletConnectedError } from '../utils/errors';
import BrowserConnection from './connection/Browser';

interface EventsBase {
accountsChange: (accounts: AccountRpc[]) => void;
disconnect: (p: any) => void;
}

export default abstract class WalletConnectorFrameBase<T extends {}>
extends EventEmitter<EventsBase | T> {
#rpcClient?: RpcClient<WalletApi, AeppApi>;

#getRpcClient(): RpcClient<WalletApi, AeppApi> {
if (this.#rpcClient == null) throw new NoWalletConnectedError('You are not connected to Wallet');
return this.#rpcClient;
}

/**
* Is connected to wallet
*/
get isConnected(): boolean {
return this.#rpcClient != null;
}

#accounts: AccountRpc[] = [];

/**
* Accounts provided by wallet over subscription
*/
get accounts(): AccountRpc[] {
return this.#accounts;
}

protected constructor() {
super();
}

protected abstract _updateNetwork(params: Network): void;

#updateAccounts(params: Accounts): void {
const addresses = [...new Set(
[...Object.keys(params.current), ...Object.keys(params.connected)],
)] as Encoded.AccountAddress[];
this.#accounts = addresses.map((address) => new AccountRpc(this.#getRpcClient(), address));
this.emit('accountsChange', this.#accounts);
}

static async _connect(
name: string,
connection: BrowserConnection,
connector: WalletConnectorFrameBase<any>,
connectNode: boolean,
): Promise<void> {
let disconnectParams: any;

const client = new RpcClient<WalletApi, AeppApi>(
connection,
() => {
connector.#rpcClient = undefined;
connector.#accounts = [];
connector.emit('disconnect', disconnectParams);
},
{
[METHODS.updateAddress]: connector.#updateAccounts.bind(connector),
[METHODS.updateNetwork]: connector._updateNetwork.bind(connector),
[METHODS.closeConnection]: (params) => {
disconnectParams = params;
client.connection.disconnect();
},
[METHODS.readyToConnect]: () => {},
},
);
connector.#rpcClient = client;
const walletInfo = await connector.#rpcClient
.request(METHODS.connect, { name, version: RPC_VERSION, connectNode });
connector._updateNetwork(walletInfo);
}

/**
* Disconnect from wallet
*/
disconnect(): void {
const client = this.#getRpcClient();
client.notify(METHODS.closeConnection, { reason: 'bye' });
client.connection.disconnect();
}

/**
* Request accounts from wallet
*/
async getAccounts(): Promise<AccountRpc[]> {
const client = this.#getRpcClient();
const addresses = await client.request(METHODS.address, undefined);
return addresses.map((address) => new AccountRpc(client, address));
}

/**
* Subscribe for wallet accounts, get account updates adding handler to `accountsChange` event
* @param type - Subscription type
* @param value - Should be one of 'current' (the selected account), 'connected' (all)
* @returns Accounts from wallet
*/
async subscribeAccounts(
type: SUBSCRIPTION_TYPES,
value: 'current' | 'connected',
): Promise<AccountRpc[]> {
const result = await this.#getRpcClient().request(METHODS.subscribeAddress, { type, value });
this.#updateAccounts(result.address);
return this.#accounts;
}
}
45 changes: 45 additions & 0 deletions src/aepp-wallet-communication/WalletConnectorFrameWithNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Network } from './rpc/types';
import { RpcConnectionError } from '../utils/errors';
import Node from '../Node';
import BrowserConnection from './connection/Browser';
import WalletConnectorFrameBase from './WalletConnectorFrameBase';

interface EventsWithNode {
nodeChange: (node: Node) => void;
}

/**
* Connect to wallet as iframe/web-extension, asks wallet to provide node url
* In comparison with WalletConnectorFrame, this would work better for decentralized applications
* @category aepp wallet communication
*/
export default class WalletConnectorFrameWithNode extends WalletConnectorFrameBase<EventsWithNode> {
#node: Node = null as unknown as Node;

/**
* The node instance provided by wallet
*/
get node(): Node {
return this.#node;
}

protected override _updateNetwork(params: Network): void {
if (params.node?.url == null) throw new RpcConnectionError('Missing URLs of the Node');
this.#node = new Node(params.node.url);
this.emit('nodeChange', this.#node);
}

/**
* Connect to wallet
* @param name - Aepp name
* @param connection - Wallet connection object
*/
static async connect(
name: string,
connection: BrowserConnection,
): Promise<WalletConnectorFrameWithNode> {
const connector = new WalletConnectorFrameWithNode();
await super._connect(name, connection, connector, true);
return connector;
}
}
2 changes: 2 additions & 0 deletions src/index-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export { default as AeSdkMethods } from './AeSdkMethods';
export { default as AeSdkBase } from './AeSdkBase';
export { default as AeSdk } from './AeSdk';
export { default as AeSdkAepp } from './AeSdkAepp';
export { default as WalletConnectorFrame } from './aepp-wallet-communication/WalletConnectorFrame';
export { default as WalletConnectorFrameWithNode } from './aepp-wallet-communication/WalletConnectorFrameWithNode';
export { default as AeSdkWallet } from './AeSdkWallet';
export { default as Node } from './Node';
export { default as verifyTransaction } from './tx/validator';
Expand Down

0 comments on commit c3570ac

Please sign in to comment.