Skip to content

Commit 9ed833a

Browse files
authored
Merge pull request #2063 from aeternity/ledger-ble
docs(account): connect to Ledger over Bluetooth in example aepp
2 parents e49753f + 7580707 commit 9ed833a

File tree

2 files changed

+94
-15
lines changed

2 files changed

+94
-15
lines changed

examples/browser/aepp/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"dependencies": {
1010
"@aeternity/aepp-calldata": "^1.5.1",
1111
"@aeternity/aepp-sdk": "file:../../..",
12-
"@ledgerhq/hw-transport-webusb": "^6.27.17",
12+
"@ledgerhq/hw-transport-web-ble": "^6.29.4",
13+
"@ledgerhq/hw-transport-webusb": "^6.29.4",
1314
"buffer": "^6.0.3",
1415
"core-js": "^3.32.1",
1516
"tailwindcss": "^2.2.19",

examples/browser/aepp/src/components/ConnectLedger.vue

+92-14
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,70 @@
11
<template>
22
<div class="group">
3+
<template v-if="!accountFactory">
4+
<button :disabled="!isUsbSupported" @click="() => connect(false)">Connect over USB</button>
5+
<button :disabled="!isBleSupported" @click="() => connect(true)">Connect over BLE</button>
6+
</template>
7+
<template v-else>
8+
<div>
9+
<div>Device</div>
10+
<div>{{ accountFactory.transport.deviceModel.productName }}</div>
11+
</div>
12+
<div v-if="accounts.length">
13+
<div>Accounts</div>
14+
<div>{{ accounts.map((account) => account.address.slice(0, 8)).join(', ') }}</div>
15+
</div>
16+
</template>
317
<div v-if="status">
418
<div>Connection status</div>
519
<div>{{ status }}</div>
620
</div>
7-
<button v-else-if="!accountFactory" @click="connect">Connect</button>
8-
<template v-else>
21+
<template v-else-if="accountFactory">
922
<button @click="disconnect">Disconnect</button>
1023
<button @click="() => addAccount(true)">Add Account</button>
1124
<button @click="() => addAccount(false)">Add Account no Confirm</button>
1225
<button v-if="accounts.length > 1" @click="switchAccount">Switch Account</button>
1326
<button @click="discoverAccounts">Discover Accounts</button>
1427
<button @click="switchNode">Switch Node</button>
15-
<div v-if="accounts.length">
16-
<div>Accounts</div>
17-
<div>{{ accounts.map((account) => account.address.slice(0, 8)).join(', ') }}</div>
18-
</div>
1928
</template>
2029
</div>
2130
</template>
2231

2332
<script>
2433
import { AccountLedgerFactory } from '@aeternity/aepp-sdk';
2534
import { mapState } from 'vuex';
26-
import TransportWebUSB from '@ledgerhq/hw-transport-webusb';
35+
import TransportWebUsb from '@ledgerhq/hw-transport-webusb';
36+
import TransportWebBle from '@ledgerhq/hw-transport-web-ble';
37+
import { listen } from '@ledgerhq/logs';
38+
39+
// TODO: remove after fixing https://github.com/LedgerHQ/ledgerjs/issues/352#issuecomment-615917351
40+
class TransportWebBleAndroidFix extends TransportWebBle {
41+
static async open(device, ...args) {
42+
if (!navigator.userAgent.includes('Mobi')) return super.open(device, ...args);
43+
const getPrimaryServicesOrig = device.gatt?.getPrimaryServices;
44+
if (getPrimaryServicesOrig == null) return super.open(device, ...args);
45+
device.gatt.getPrimaryServices = async () => {
46+
const [service] = await getPrimaryServicesOrig.call(device.gatt);
47+
const getCharacteristicOrig = service.getCharacteristic;
48+
service.getCharacteristic = async (id) => {
49+
const characteristic = await getCharacteristicOrig.call(service, id);
50+
if (id === '13d63400-2c97-0004-0002-4c6564676572') {
51+
const writeValueOrig = characteristic.writeValue;
52+
let delayed = false;
53+
characteristic.writeValue = async (data) => {
54+
if (!delayed) {
55+
await new Promise((resolve) => setTimeout(resolve, 250));
56+
delayed = true;
57+
}
58+
return writeValueOrig.call(characteristic, data);
59+
};
60+
}
61+
return characteristic;
62+
};
63+
return [service];
64+
};
65+
return super.open(device, ...args);
66+
}
67+
}
2768
2869
export default {
2970
created() {
@@ -32,27 +73,53 @@ export default {
3273
data: () => ({
3374
status: '',
3475
accounts: [],
76+
isUsbSupported: false,
77+
isBleSupported: false,
3578
}),
3679
computed: mapState(['aeSdk']),
3780
methods: {
38-
async connect() {
81+
async connect(isBle) {
82+
let transport;
3983
try {
4084
this.status = 'Waiting for Ledger response';
41-
const transport = await TransportWebUSB.create();
42-
this.accountFactory = new AccountLedgerFactory(transport);
85+
transport = await (isBle ? TransportWebBleAndroidFix : TransportWebUsb).create();
86+
transport.on('disconnect', () => this.reset());
87+
const factory = new AccountLedgerFactory(transport);
88+
await factory.ensureReady();
89+
this.accountFactory = factory;
90+
this.status = '';
4391
} catch (error) {
44-
if (error.name === 'TransportOpenUserCancelled') return;
92+
transport?.close();
93+
if (error.name === 'TransportOpenUserCancelled') {
94+
this.status = '';
95+
return;
96+
}
97+
if (error.name === 'LockedDeviceError') {
98+
this.status = 'Device is locked, please unlock it';
99+
return;
100+
}
101+
if (error.message.includes('UNKNOWN_APDU')) {
102+
this.status = 'Ensure that aeternity app is opened on Ledger HW';
103+
return;
104+
}
105+
if (error.name === 'UnsupportedVersionError') {
106+
this.status = error.message;
107+
return;
108+
}
109+
this.status = 'Unknown error';
45110
throw error;
46-
} finally {
47-
this.status = '';
48111
}
49112
},
50-
async disconnect() {
113+
reset() {
51114
this.accountFactory = null;
52115
this.accounts = [];
53116
this.$store.commit('setAddress', undefined);
54117
if (Object.keys(this.aeSdk.accounts).length) this.aeSdk.removeAccount(this.aeSdk.address);
55118
},
119+
async disconnect() {
120+
await this.accountFactory.transport.close();
121+
this.reset();
122+
},
56123
async addAccount(confirm) {
57124
try {
58125
this.status = 'Waiting for Ledger response';
@@ -95,5 +162,16 @@ export default {
95162
this.$store.commit('setAddress', account.address);
96163
},
97164
},
165+
async mounted() {
166+
this.isUsbSupported = await TransportWebUsb.isSupported();
167+
this.isBleSupported = await TransportWebBle.isSupported();
168+
this.unsubscribeLedgerLog = listen(({ type, id, date, message }) => {
169+
console.log(type, id, date.toLocaleTimeString(), message);
170+
});
171+
},
172+
async beforeUnmount() {
173+
if (this.accountFactory) this.disconnect();
174+
this.unsubscribeLedgerLog();
175+
},
98176
};
99177
</script>

0 commit comments

Comments
 (0)