Skip to content

Commit

Permalink
Merge pull request #1866 from aeternity/feature/refactor-rpc
Browse files Browse the repository at this point in the history
Extract WalletConnectorFrame, deprecate AeSdkAepp
  • Loading branch information
davidyuk authored Sep 26, 2024
2 parents 908930d + 33a081a commit 113f28f
Show file tree
Hide file tree
Showing 14 changed files with 375 additions and 24 deletions.
4 changes: 4 additions & 0 deletions examples/browser/aepp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"dependencies": {
"@aeternity/aepp-calldata": "^1.5.1",
"@aeternity/aepp-sdk": "file:../../..",
"@ledgerhq/hw-transport-webusb": "^6.27.17",
"buffer": "^6.0.3",
"core-js": "^3.32.1",
"tailwindcss": "^2.2.19",
Expand All @@ -21,6 +22,9 @@
"sass": "^1.66.1",
"sass-loader": "^13.3.2"
},
"peerDependencies": {
"webpack": "^5.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
Expand Down
154 changes: 144 additions & 10 deletions examples/browser/aepp/src/Connect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,67 @@
>
Cancel detection
</button>

<template v-if="walletConnected">
<br>
<button @click="getAccounts">
Get accounts
</button>
<button @click="subscribeAccounts('subscribe', 'current')">
Subscribe current
</button>
<button @click="subscribeAccounts('unsubscribe', 'current')">
Unsubscribe current
</button>
<button @click="subscribeAccounts('subscribe', 'connected')">
Subscribe connected
</button>
<button @click="subscribeAccounts('unsubscribe', 'connected')">
Unsubscribe connected
</button>

<div>
<div>RPC Accounts</div>
<div>{{ rpcAccounts.map((account) => account.address.slice(0, 8)).join(', ') }}</div>
</div>
</template>
</div>

<h2>Ledger Hardware Wallet</h2>
<div class="group">
<template v-if="ledgerStatus">
<div>
<div>Connection status</div>
<div>{{ ledgerStatus }}</div>
</div>
</template>
<button
v-else-if="!ledgerAccountFactory"
@click="connectLedger"
>
Connect
</button>
<template v-else>
<button @click="disconnectLedger">
Disconnect
</button>
<button @click="addLedgerAccount">
Add Account
</button>
<button
v-if="ledgerAccounts.length > 1"
@click="switchLedgerAccount"
>
Switch Account
</button>
<button @click="switchNode">
Switch Node
</button>
<div v-if="ledgerAccounts.length">
<div>Ledger Accounts</div>
<div>{{ ledgerAccounts.map((account) => account.address.slice(0, 8)).join(', ') }}</div>
</div>
</template>
</div>

<div class="group">
Expand All @@ -58,8 +119,10 @@
<script>
import {
walletDetector, BrowserWindowMessageConnection, RpcConnectionDenyError, RpcRejectedByUserError,
WalletConnectorFrame, AccountLedgerFactory,
} from '@aeternity/aepp-sdk';
import { mapState } from 'vuex';
import TransportWebUSB from '@ledgerhq/hw-transport-webusb';
export default {
data: () => ({
Expand All @@ -70,6 +133,10 @@ export default {
reverseIframeWalletUrl: process.env.VUE_APP_WALLET_URL ?? `http://${location.hostname}:9000`,
walletInfo: null,
cancelWalletDetection: null,
rpcAccounts: [],
ledgerStatus: '',
ledgerAccountFactory: null,
ledgerAccounts: [],
}),
computed: {
...mapState(['aeSdk']),
Expand All @@ -79,6 +146,54 @@ export default {
},
},
methods: {
async connectLedger() {
try {
this.ledgerStatus = 'Waiting for Ledger response';
const transport = await TransportWebUSB.create();
this.ledgerAccountFactory = new AccountLedgerFactory(transport);
} catch (error) {
if (error.name === 'TransportOpenUserCancelled') return;
throw error;
} finally {
this.ledgerStatus = '';
}
},
async disconnectLedger() {
this.ledgerAccountFactory = null;
this.ledgerAccounts = [];
this.$store.commit('setAddress', undefined);
if (Object.keys(this.aeSdk.accounts).length) this.aeSdk.removeAccount(this.aeSdk.address);
},
async addLedgerAccount() {
try {
this.ledgerStatus = 'Waiting for Ledger response';
const idx = this.ledgerAccounts.length;
const account = await this.ledgerAccountFactory.initialize(idx);
this.ledgerStatus = `Ensure that ${account.address} is displayed on Ledger HW screen`;
await this.ledgerAccountFactory.getAddress(idx, true);
this.ledgerAccounts.push(account);
this.setAccount(this.ledgerAccounts[0]);
} catch (error) {
if (error.statusCode === 0x6985) return;
throw error;
} finally {
this.ledgerStatus = '';
}
},
switchLedgerAccount() {
this.ledgerAccounts.push(this.ledgerAccounts.shift());
this.setAccount(this.ledgerAccounts[0]);
},
async switchNode() {
await this.setNode(this.$store.state.networkId === 'ae_mainnet' ? 'ae_uat' : 'ae_mainnet');
},
async getAccounts() {
this.rpcAccounts = await this.walletConnector.getAccounts();
if (this.rpcAccounts.length) this.setAccount(this.rpcAccounts[0]);
},
async subscribeAccounts(type, value) {
await this.walletConnector.subscribeAccounts(type, value);
},
async detectWallets() {
if (this.connectMethod === 'reverse-iframe') {
this.reverseIframe = document.createElement('iframe');
Expand All @@ -93,6 +208,7 @@ export default {
stopDetection();
resolve(newWallet.getConnection());
this.cancelWalletDetection = null;
this.walletInfo = newWallet.info;
}
});
this.cancelWalletDetection = () => {
Expand All @@ -103,25 +219,43 @@ 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.rpcAccounts = [];
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.rpcAccounts = accounts;
if (accounts.length) this.setAccount(accounts[0]);
});
} catch (error) {
if (
error.message === 'Wallet detection cancelled'
Expand All @@ -134,7 +268,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,26 +1,18 @@
import { shallowRef } from 'vue';
import { createStore } from 'vuex';
import { AeSdkAepp, Node, CompilerHttp } from '@aeternity/aepp-sdk';
import { AeSdk, Node, CompilerHttp } from '@aeternity/aepp-sdk';

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: shallowRef(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: shallowRef(new AeSdk({
nodes: [
{ name: 'testnet', instance: new Node('https://testnet.aeternity.io') },
{ name: 'mainnet', instance: new Node('https://mainnet.aeternity.io') },
],
onCompiler: new CompilerHttp('https://v8.compiler.aepps.com'),
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
2 changes: 1 addition & 1 deletion examples/browser/aepp/src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ body {
}

button {
@extend .w-40, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;
@extend .w-44, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;

&:disabled {
@extend .bg-purple-300, .cursor-not-allowed;
Expand Down
6 changes: 6 additions & 0 deletions examples/browser/aepp/vue.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
const { defineConfig } = require('@vue/cli-service');
const webpack = require('webpack');

module.exports = defineConfig({
publicPath: process.env.PUBLIC_PATH ?? '/',
devServer: {
port: 9001,
},
configureWebpack: {
plugins: [
new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'] }),
],
},
});
2 changes: 1 addition & 1 deletion examples/browser/wallet-iframe/src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ body {
}

button {
@extend .w-40, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;
@extend .w-44, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;

&:disabled {
@extend .bg-purple-300, .cursor-not-allowed;
Expand Down
2 changes: 1 addition & 1 deletion examples/browser/wallet-web-extension/src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ body {
}

button {
@extend .w-40, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;
@extend .w-44, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;

&:disabled {
@extend .bg-purple-300, .cursor-not-allowed;
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 @@ -70,6 +70,7 @@
"bs58": "^6.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;
}
}
Loading

0 comments on commit 113f28f

Please sign in to comment.