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

Fix BrowserRuntime in Firefox and pre-release fixes #1735

Merged
merged 13 commits into from Jan 13, 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: 1 addition & 1 deletion docker/accounts_test.json
@@ -1,3 +1,3 @@
{
"ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR": 100000000000000000000000000000000000000000
"ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E": 100000000000000000000000000000000000000000
}
2 changes: 1 addition & 1 deletion docs/guides/aens.md
Expand Up @@ -311,7 +311,7 @@ console.log(nameTransferTx)
fee: 17300000000000,
nameId: 'nm_1Cz5HGY8PMWZxNrM6s51CtsJZDU3DDT1LdmpEipa3DRghyGz5',
nonce: 33,
recipientId: 'ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR',
recipientId: 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E',
type: 'NameTransferTx',
version: 1
},
Expand Down
8 changes: 5 additions & 3 deletions examples/browser/aepp/src/Basic.vue
Expand Up @@ -57,6 +57,7 @@

<script>
import { mapState, mapGetters } from 'vuex';
import { encode, Encoding } from '@aeternity/aepp-sdk';
import Value from './Value.vue';

export default {
Expand All @@ -80,8 +81,7 @@ export default {
({ aeSdk, address, networkId }) => [aeSdk, address, networkId],
([aeSdk, address]) => {
if (!aeSdk) return;
this.compilerVersionPromise = aeSdk.compilerApi.apiVersion()
.then(({ apiVersion }) => apiVersion);
this.compilerVersionPromise = aeSdk.compilerApi.version();
this.balancePromise = aeSdk.getBalance(address);
this.heightPromise = aeSdk.getHeight();
this.nodeInfoPromise = aeSdk.getNodeInfo();
Expand All @@ -91,7 +91,9 @@ export default {
},
methods: {
spend() {
return this.aeSdk.spend(this.spendAmount, this.spendTo, { payload: this.spendPayload });
return this.aeSdk.spend(this.spendAmount, this.spendTo, {
payload: encode(new TextEncoder().encode(this.spendPayload), Encoding.Bytearray),
});
},
},
};
Expand Down
55 changes: 35 additions & 20 deletions examples/browser/aepp/src/Connect.vue
Expand Up @@ -15,7 +15,8 @@
</div>
<button
v-if="connectMethod && !walletConnected"
@click="connectPromise = connect().then(() => 'Ready')"
:disabled="walletConnecting"
@click="connect"
>
Connect
</button>
Expand All @@ -30,7 +31,13 @@
<div class="group">
<div>
<div>SDK status</div>
<Value :value="connectPromise" />
<div>
{{
(walletConnected && 'Wallet connected')
|| (walletConnecting && 'Wallet connecting')
|| 'Ready to connect to wallet'
}}
</div>
</div>
<div>
<div>Wallet name</div>
Expand All @@ -40,16 +47,16 @@
</template>

<script>
import { walletDetector, BrowserWindowMessageConnection } from '@aeternity/aepp-sdk';
import {
walletDetector, BrowserWindowMessageConnection, RpcConnectionDenyError,
} from '@aeternity/aepp-sdk';
import { mapGetters } from 'vuex';
import Value from './Value.vue';

export default {
components: { Value },
data: () => ({
connectMethod: 'default',
walletConnected: false,
connectPromise: null,
walletConnecting: null,
reverseIframe: null,
reverseIframeWalletUrl: 'http://localhost:9000',
walletInfo: null,
Expand All @@ -70,14 +77,8 @@ export default {
const handleWallets = async ({ wallets, newWallet }) => {
newWallet = newWallet || Object.values(wallets)[0];
if (confirm(`Do you want to connect to wallet ${newWallet.info.name} with id ${newWallet.info.id}`)) {
console.log('newWallet', newWallet);
stopScan();

this.walletInfo = await this.aeSdk.connectToWallet(newWallet.getConnection());
this.walletConnected = true;
const { address: { current } } = await this.aeSdk.subscribeAddress('subscribe', 'connected');
this.$store.commit('aeSdk/setAddress', Object.keys(current)[0]);
resolve();
resolve(newWallet.getConnection());
}
};

Expand All @@ -86,14 +87,28 @@ export default {
});
},
async connect() {
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);
this.walletConnecting = true;
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);
}
await this.$store.dispatch('aeSdk/initialize');
const connection = await this.scanForWallets();
try {
this.walletInfo = await this.aeSdk.connectToWallet(connection);
} catch (error) {
if (error instanceof RpcConnectionDenyError) connection.disconnect();
throw error;
}
this.walletConnected = true;
const { address: { current } } = await this.aeSdk.subscribeAddress('subscribe', 'connected');
this.$store.commit('aeSdk/setAddress', Object.keys(current)[0]);
} finally {
this.walletConnecting = false;
}
await this.$store.dispatch('aeSdk/initialize');
await this.scanForWallets();
},
async disconnect() {
await this.aeSdk.disconnectWallet();
Expand Down
10 changes: 1 addition & 9 deletions examples/browser/aepp/src/styles.scss
Expand Up @@ -26,16 +26,8 @@ button {
}
}

h1, h2 {
@extend .mt-2, .font-bold;
}

h1 {
@extend .text-3xl;
}

h2 {
@extend .text-2xl;
@extend .mt-2, .font-bold, .text-2xl;
}

input:not([type=radio]), textarea {
Expand Down
2 changes: 1 addition & 1 deletion examples/browser/wallet-iframe/src/App.vue
Expand Up @@ -115,7 +115,7 @@ export default {
{ name: 'ae_mainnet', instance: new Node('https://mainnet.aeternity.io') },
],
accounts: [
new AccountMemoryProtected('bf66e1c256931870908a649572ed0257876bb84e3cdf71efb12f56c7335fad54d5cf08400e988222f26eb4b02c8f89077457467211a6e6d955edb70749c6a33b'),
new AccountMemoryProtected('9ebd7beda0c79af72a42ece3821a56eff16359b6df376cf049aee995565f022f840c974b97164776454ba119d84edc4d6058a8dec92b6edc578ab2d30b4c4200'),
AccountMemoryProtected.generate(),
],
onCompiler: new CompilerHttp('https://v7.compiler.aepps.com'),
Expand Down
10 changes: 1 addition & 9 deletions examples/browser/wallet-iframe/src/styles.scss
Expand Up @@ -22,16 +22,8 @@ button {
}
}

h1, h2 {
@extend .mt-2, .font-bold;
}

h1 {
@extend .text-3xl;
}

h2 {
@extend .text-2xl;
@extend .mt-2, .font-bold, .text-2xl;
}

.group {
Expand Down
1 change: 1 addition & 0 deletions examples/browser/wallet-web-extension/package.json
Expand Up @@ -9,6 +9,7 @@
"dependencies": {
"@aeternity/aepp-sdk": "file:../../..",
"core-js": "^3.22.6",
"tailwindcss": "^2.2.19",
"vue": "^2.6.14",
"webextension-polyfill": "^0.9.0"
},
Expand Down
62 changes: 54 additions & 8 deletions examples/browser/wallet-web-extension/src/Popup.vue
@@ -1,13 +1,59 @@
<template>
<div>
<h3>Wallet WebExtension</h3>
<img src="../public/icons/128.png" alt="Logo">
<div class="popup">
<template v-if="popupParameters">
<h2>Aepp at {{ aeppOrigin }} want to {{ action }}</h2>
<div class="group">
<div>
<div>Request details</div>
<div>{{ JSON.stringify(popupParameters, null, 2) }}</div>
</div>

<button @click="() => respond(true)">
Confirm
</button>
<button @click="() => respond(false)">
Reject
</button>
</div>
</template>

<h2 v-else>Wallet WebExtension</h2>
</div>
</template>

<style>
html {
width: 400px;
height: 400px;
<script>
import browser from 'webextension-polyfill';
import { unpackTx } from '@aeternity/aepp-sdk';

export default {
data: () => ({
aeppOrigin: null,
action: null,
popupId: null,
popupParameters: null,
isResponded: false,
}),
mounted() {
const data = new URL(location).searchParams.get('data');
if (data != null) {
const { aeppOrigin, action, popupId, ...params } = JSON.parse(data);
if (params.transaction) params.unpackedTx = unpackTx(params.transaction);
this.aeppOrigin = aeppOrigin;
this.action = action;
this.popupId = popupId;
this.popupParameters = params;
window.addEventListener('beforeunload', () => this.respond(false));
}
},
methods: {
async respond(response) {
if (this.isResponded) return;
await browser.runtime.sendMessage({ response, popupId: this.popupId });
this.isResponded = true;
window.close();
},
},
}
</style>
</script>

<style lang="scss" src="./styles.scss" />
68 changes: 49 additions & 19 deletions examples/browser/wallet-web-extension/src/background.js
Expand Up @@ -4,27 +4,52 @@ import {
WALLET_TYPE, RpcConnectionDenyError, RpcRejectedByUserError,
} from '@aeternity/aepp-sdk';

let popupCounter = 0;
async function confirmInPopup(parameters) {
const popupUrl = new URL(browser.runtime.getURL('./popup.html'));
const popupId = popupCounter;
popupCounter += 1;
popupUrl.searchParams.set('data', JSON.stringify({ ...parameters, popupId }));
await browser.windows.create({
url: popupUrl.toString(),
type: 'popup',
height: 600,
width: 600,
});
return new Promise((resolve) => {
const handler = (message, sender, sendResponse) => {
if (message.popupId !== popupId) return;
resolve(message.response);
sendResponse();
browser.runtime.onMessage.removeListener(handler);
};
browser.runtime.onMessage.addListener(handler);
});
}

const aeppInfo = {};
const genConfirmCallback = (actionName) => (aeppId, parameters, origin) => {
if (!confirm([
`Client ${aeppInfo[aeppId].name} with id ${aeppId} at ${origin} want to ${actionName}`,
JSON.stringify(parameters, null, 2),
].join('\n'))) {
throw new RpcRejectedByUserError();
}
const genConfirmCallback = (action) => async (aeppId, parameters, aeppOrigin) => {
const isConfirmed = await confirmInPopup({
...parameters,
action,
aeppId,
aeppInfo: aeppInfo[aeppId],
aeppOrigin,
});
if (!isConfirmed) throw new RpcRejectedByUserError();
};

class AccountMemoryProtected extends MemoryAccount {
async signTransaction(tx, { aeppRpcClientId: id, aeppOrigin, ...options } = {}) {
async signTransaction(transaction, { aeppRpcClientId: id, aeppOrigin, ...options } = {}) {
if (id != null) {
genConfirmCallback(`sign transaction ${tx}`)(id, options, aeppOrigin);
await genConfirmCallback('sign transaction')(id, { ...options, transaction }, aeppOrigin);
}
return super.signTransaction(tx, options);
return super.signTransaction(transaction, options);
}

async signMessage(message, { aeppRpcClientId: id, aeppOrigin, ...options } = {}) {
if (id != null) {
genConfirmCallback(`sign message ${message}`)(id, options, aeppOrigin);
await genConfirmCallback('sign message')(id, { ...options, message }, aeppOrigin);
}
return super.signMessage(message, options);
}
Expand All @@ -42,20 +67,24 @@ const aeSdk = new AeSdkWallet({
instance: new Node('https://testnet.aeternity.io'),
}],
accounts: [
new AccountMemoryProtected('bf66e1c256931870908a649572ed0257876bb84e3cdf71efb12f56c7335fad54d5cf08400e988222f26eb4b02c8f89077457467211a6e6d955edb70749c6a33b'),
new AccountMemoryProtected('9ebd7beda0c79af72a42ece3821a56eff16359b6df376cf049aee995565f022f840c974b97164776454ba119d84edc4d6058a8dec92b6edc578ab2d30b4c4200'),
AccountMemoryProtected.generate(),
],
id: browser.runtime.id,
type: WALLET_TYPE.extension,
name: 'Wallet WebExtension',
onConnection: (aeppId, params, origin) => {
if (!confirm(`Client ${params.name} with id ${aeppId} at ${origin} want to connect`)) {
throw new RpcConnectionDenyError();
}
async onConnection(aeppId, params, aeppOrigin) {
const isConfirmed = await confirmInPopup({
action: 'connect',
aeppId,
aeppInfo: params,
aeppOrigin,
});
if (!isConfirmed) throw new RpcConnectionDenyError();
aeppInfo[aeppId] = params;
},
onDisconnect(msg, client) {
console.log('Client disconnected:', client);
onDisconnect(aeppId, payload) {
console.log('Client disconnected:', aeppId, payload);
},
onSubscription: genConfirmCallback('subscription'),
onAskAccounts: genConfirmCallback('get accounts'),
Expand All @@ -70,7 +99,8 @@ browser.runtime.onConnect.addListener((port) => {
const clientId = aeSdk.addRpcClient(connection);
// share wallet details
aeSdk.shareWalletInfo(clientId);
setInterval(() => aeSdk.shareWalletInfo(clientId), 3000);
const interval = setInterval(() => aeSdk.shareWalletInfo(clientId), 3000);
port.onDisconnect.addListener(() => clearInterval(interval));
});

console.log('Wallet initialized!');