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

Aepp-wallet connection fixes #1865

Merged
merged 5 commits into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 0 additions & 2 deletions examples/browser/aepp/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
</template>

<script>
import { mapState } from 'vuex';
import Connect from './Connect.vue';
import Basic from './Basic.vue';
import Contracts from './Contracts.vue';
Expand All @@ -53,7 +52,6 @@ export default {
Connect, Basic, Contracts, PayForTx, TypedData,
},
data: () => ({ view: '' }),
computed: mapState(['aeSdk']),
};
</script>

Expand Down
75 changes: 49 additions & 26 deletions examples/browser/aepp/src/Connect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,26 @@
</label>
<div><input v-model="reverseIframeWalletUrl"></div>
</div>

<button
v-if="walletConnected"
@click="disconnect"
>
Disconnect
</button>
<button
v-if="connectMethod && !walletConnected"
v-else-if="connectMethod"
:disabled="walletConnecting"
@click="connect"
>
Connect
</button>

<button
v-if="walletConnected"
@click="disconnect"
v-if="cancelWalletDetection"
@click="cancelWalletDetection"
>
Disconnect
Cancel detection
</button>
</div>

Expand All @@ -34,6 +42,7 @@
<div>
{{
(walletConnected && 'Wallet connected')
|| (cancelWalletDetection && 'Wallet detection')
|| (walletConnecting && 'Wallet connecting')
|| 'Ready to connect to wallet'
}}
Expand All @@ -48,7 +57,7 @@

<script>
import {
walletDetector, BrowserWindowMessageConnection, RpcConnectionDenyError,
walletDetector, BrowserWindowMessageConnection, RpcConnectionDenyError, RpcRejectedByUserError,
} from '@aeternity/aepp-sdk';
import { mapState } from 'vuex';

Expand All @@ -60,6 +69,7 @@ export default {
reverseIframe: null,
reverseIframeWalletUrl: process.env.VUE_APP_WALLET_URL ?? 'http://localhost:9000',
walletInfo: null,
cancelWalletDetection: null,
}),
computed: {
...mapState(['aeSdk']),
Expand All @@ -69,32 +79,40 @@ export default {
},
},
methods: {
async scanForWallets() {
return new Promise((resolve) => {
let stopScan;

const handleWallets = async ({ wallets, newWallet }) => {
newWallet = newWallet || Object.values(wallets)[0];
async detectWallets() {
if (this.connectMethod === 'reverse-iframe') {
this.reverseIframe = document.createElement('iframe');
this.reverseIframe.src = this.reverseIframeWalletUrl;
this.reverseIframe.style.display = 'none';
document.body.appendChild(this.reverseIframe);
}
const connection = new BrowserWindowMessageConnection();
return new Promise((resolve, reject) => {
const stopDetection = walletDetector(connection, async ({ newWallet }) => {
if (confirm(`Do you want to connect to wallet ${newWallet.info.name} with id ${newWallet.info.id}`)) {
stopScan();
stopDetection();
resolve(newWallet.getConnection());
this.cancelWalletDetection = null;
}
});
this.cancelWalletDetection = () => {
reject(new Error('Wallet detection cancelled'));
stopDetection();
this.cancelWalletDetection = null;
if (this.reverseIframe) this.reverseIframe.remove();
};

const scannerConnection = new BrowserWindowMessageConnection();
stopScan = walletDetector(scannerConnection, handleWallets);
});
},
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 {
if (this.connectMethod === 'reverse-iframe') {
this.reverseIframe = document.createElement('iframe');
this.reverseIframe.src = this.reverseIframeWalletUrl;
this.reverseIframe.style.display = 'none';
document.body.appendChild(this.reverseIframe);
}
const connection = await this.scanForWallets();
const connection = await this.detectWallets();
try {
this.walletInfo = await this.aeSdk.connectToWallet(connection);
} catch (error) {
Expand All @@ -104,14 +122,19 @@ export default {
this.walletConnected = true;
const { address: { current } } = await this.aeSdk.subscribeAddress('subscribe', 'connected');
this.$store.commit('setAddress', Object.keys(current)[0]);
} catch (error) {
if (
error.message === 'Wallet detection cancelled'
|| error instanceof RpcConnectionDenyError
|| error instanceof RpcRejectedByUserError
) return;
throw error;
} finally {
this.walletConnecting = false;
}
},
async disconnect() {
await this.aeSdk.disconnectWallet();
this.walletConnected = false;
if (this.reverseIframe) this.reverseIframe.remove();
disconnect() {
this.aeSdk.disconnectWallet();
},
},
};
Expand Down
1 change: 0 additions & 1 deletion examples/browser/aepp/src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ const store = createStore({
store.commit('setNetworkId', networkId);
},
onAddressChange: ({ current }) => store.commit('setAddress', Object.keys(current)[0]),
onDisconnect: () => alert('Aepp is disconnected'),
})),
},
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-32, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;
@extend .w-40, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;

&:disabled {
@extend .bg-purple-300, .cursor-not-allowed;
Expand Down
90 changes: 67 additions & 23 deletions examples/browser/wallet-iframe/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,26 @@
<div>Balance</div>
<Value :value="balancePromise" />
</div>
<div>
<div>RPC client</div>
<div>status: {{ clientStatus ?? 'no client' }}, id: {{ clientId ?? 'not defined' }}</div>
</div>

<button @click="switchAccount">Switch Account</button>
<button @click="switchNode">Switch Node</button>
<button @click="disconnect">Disconnect</button>

<button
v-if="clientStatus === 'CONNECTED'"
@click="disconnect"
>
Disconnect
</button>
<button
v-else
@click="() => (stopSharingWalletInfo ?? shareWalletInfo)()"
>
{{ stopSharingWalletInfo ? 'Stop sharing' : 'Share wallet info' }}
</button>
</div>

<iframe
Expand All @@ -30,7 +46,7 @@
<script>
import {
MemoryAccount, generateKeyPair, AeSdkWallet, Node, CompilerHttp,
BrowserWindowMessageConnection, METHODS, WALLET_TYPE,
BrowserWindowMessageConnection, METHODS, WALLET_TYPE, RPC_STATUS,
RpcConnectionDenyError, RpcRejectedByUserError, unpackTx, decodeFateValue,
} from '@aeternity/aepp-sdk';
import Value from './Value.vue';
Expand All @@ -43,24 +59,42 @@ export default {
nodeName: '',
address: '',
balancePromise: null,
clientId: null,
clientStatus: null,
stopSharingWalletInfo: null,
}),
methods: {
async shareWalletInfo(clientId, { interval = 5000, attemps = 5 } = {}) {
this.aeSdk.shareWalletInfo(clientId);
while (attemps) {
await new Promise((resolve) => {
setTimeout(resolve, interval);
});
this.aeSdk.shareWalletInfo(clientId);
attemps -= 1;
shareWalletInfo({ interval = 5000, attempts = 5 } = {}) {
const target = this.runningInFrame ? window.parent : this.$refs.aepp.contentWindow;
const connection = new BrowserWindowMessageConnection({ target });
this.clientId = this.aeSdk.addRpcClient(connection);

this.aeSdk.shareWalletInfo(this.clientId);
const intervalId = setInterval(() => {
this.aeSdk.shareWalletInfo(this.clientId);
attempts -= 1;
if (!attempts) return this.stopSharingWalletInfo();
}, interval);

this.stopSharingWalletInfo = () => {
clearInterval(intervalId);
// TODO: replace with clientStatus
const client = this.aeSdk._getClient(this.clientId);
if (client.status === RPC_STATUS.WAITING_FOR_CONNECTION_REQUEST) {
this.aeSdk.removeRpcClient(this.clientId);
}
this.stopSharingWalletInfo = null;
}
console.log('Finish sharing wallet info');
},
disconnect() {
Object.values(this.aeSdk.rpcClients).forEach((client) => {
client.notify(METHODS.closeConnection);
client.disconnect();
});
// TODO: move to removeRpcClient (would be a semi-breaking change)
const client = this.aeSdk._getClient(this.clientId);
if (client.status === RPC_STATUS.CONNECTED) {
client.rpc.notify(METHODS.closeConnection, null);
}

this.aeSdk.removeRpcClient(this.clientId);
this.clientId = null;
},
async switchAccount() {
this.address = this.aeSdk.addresses().find((a) => a !== this.address);
Expand All @@ -72,6 +106,14 @@ export default {
.find((name) => name !== this.nodeName);
this.aeSdk.selectNode(this.nodeName);
},
updateClientStatus() {
if (!this.clientId) {
this.clientStatus = null;
return;
}
const client = this.aeSdk._getClient(this.clientId);
this.clientStatus = client.status;
},
},
mounted() {
const aeppInfo = {};
Expand Down Expand Up @@ -116,7 +158,6 @@ export default {
}
}

let clientId;
this.aeSdk = new AeSdkWallet({
id: window.origin,
type: WALLET_TYPE.window,
Expand All @@ -135,29 +176,32 @@ export default {
throw new RpcConnectionDenyError();
}
aeppInfo[aeppId] = params;
setTimeout(() => this.stopSharingWalletInfo());
},
onSubscription: genConfirmCallback('subscription'),
onAskAccounts: genConfirmCallback('get accounts'),
onDisconnect() {
this.shareWalletInfo(clientId);
onDisconnect: (clientId) => {
console.log('disconnected client', clientId);
this.clientId = null;
},
});

if (this.runningInFrame) this.shareWalletInfo();

this.nodeName = this.aeSdk.selectedNodeName;
[this.address] = this.aeSdk.addresses();

const target = this.runningInFrame ? window.parent : this.$refs.aepp.contentWindow;
const connection = new BrowserWindowMessageConnection({ target });
clientId = this.aeSdk.addRpcClient(connection);
this.shareWalletInfo(clientId);

this.$watch(
({ address, nodeName }) => [address, nodeName],
([address]) => {
this.balancePromise = this.aeSdk.getBalance(address);
},
{ immediate: true },
);

// TODO: replace setInterval with subscription after refactoring
setInterval(() => this.updateClientStatus(), 1000);
this.$watch(({ clientId }) => [clientId], () => this.updateClientStatus(), { immediate: true });
},
};
</script>
Expand Down
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-32, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;
@extend .w-40, .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-32, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;
@extend .w-40, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;

&:disabled {
@extend .bg-purple-300, .cursor-not-allowed;
Expand Down