Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract WalletConnectorFrame, deprecate AeSdkAepp #1866

Merged
merged 4 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/browser/aepp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"dependencies": {
"@aeternity/aepp-sdk": "file:../../..",
"@ledgerhq/hw-transport-webusb": "^6.27.17",
"buffer": "^6.0.3",
"core-js": "^3.22.6",
"tailwindcss": "^2.2.19",
Expand All @@ -20,6 +21,9 @@
"sass": "^1.52.1",
"sass-loader": "^13.0.0"
},
"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://localhost: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,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
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 @@ -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;
}
}
Loading