Skip to content

Commit

Permalink
Merge pull request #358 from Concordium/fix-windows-ledger-connection…
Browse files Browse the repository at this point in the history
…-issue

Fix windows ledger connection issue
  • Loading branch information
orhoj authored Mar 7, 2024
2 parents bf600e9 + be9624c commit 3f57f0e
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 101 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 1.7.3

### Fixed

- An issue reported on Windows where the Ledger connectivity state was faulty, preventing users from signing transactions.

## 1.7.2

### Fixed
Expand Down
5 changes: 3 additions & 2 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "concordium-desktop-wallet",
"productName": "concordium-desktop-wallet",
"description": "concordium-desktop-wallet",
"version": "1.7.2",
"version": "1.7.3",
"main": "./main.prod.js",
"author": {
"name": "Concordium Software",
Expand All @@ -20,7 +20,8 @@
"@types/ledgerhq__hw-transport-node-hid": "^4.22.2",
"knex": "0.95.5",
"node-abi": "^2.30.0",
"sqlite3": "5.0.0"
"sqlite3": "5.0.0",
"usb": "2.9.0"
},
"resolutions": {
"buffer": "^6.0.3"
Expand Down
158 changes: 113 additions & 45 deletions app/preload/ledger/ledgerObserverImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,23 @@ import type {
Subscription,
} from '@ledgerhq/hw-transport';
import EventEmitter from 'events';
import { usb } from 'usb';
import { identifyUSBProductId, ledgerUSBVendorId } from '@ledgerhq/devices';
import ConcordiumLedgerClientMain from '../../features/ledger/ConcordiumLedgerClientMain';
import { isConcordiumApp, isOutdated } from '../../components/ledger/util';
import { LedgerSubscriptionAction } from '../../components/ledger/useLedger';
import ledgerIpcCommands from '~/constants/ledgerIpcCommands.json';
import { LedgerObserver } from './ledgerObserver';

/**
* A convenience method for opening a transport by providing
* an empty descriptor (as the descriptor is unused downstream).
* @returns a promise with a {@link TransportNodeHid}
*/
async function openTransport(): Promise<TransportNodeHid> {
return TransportNodeHid.open('');
}

export default class LedgerObserverImpl implements LedgerObserver {
concordiumClient: ConcordiumLedgerClientMain | undefined;

Expand All @@ -25,18 +36,58 @@ export default class LedgerObserverImpl implements LedgerObserver {
return this.concordiumClient;
}

private handleIfLedgerIsStillConnected(
device: usb.Device,
mainWindow: EventEmitter
) {
// Ignore events for non-Ledger USB devices.
if (device.deviceDescriptor.idVendor !== ledgerUSBVendorId) {
return;
}

openTransport()
.then(() => {
// First attempt to get the app and version from the device. This is necessary
// to flush any previous messages on the channel, and that can lead to us
// receiving a message stating that the Concordium app is still running.
this.concordiumClient
?.getAppAndVersion()
.catch(() => {})
.finally(() => {
const deviceModel = identifyUSBProductId(
device.deviceDescriptor.idProduct
);
this.updateLedgerState(
mainWindow,
deviceModel?.productName
);
return true;
});
return true;
})
.catch(() => null);
}

async subscribeLedger(mainWindow: EventEmitter): Promise<void> {
if (!this.ledgerSubscription) {
this.ledgerSubscription = TransportNodeHid.listen(
this.createLedgerObserver(mainWindow)
);

// The TransportNodeHid.listen() does not always catch all the relevant
// USB events on Windows. This is specifically a problem when opening or
// closing the Concordium app on the Ledger as it fires an attach and a
// detach event at the same time. Therefore we add a raw USB listener as a backup
// that will ensure that we maintain an intact connection state.
usb.on('detach', (event) =>
this.handleIfLedgerIsStillConnected(event, mainWindow)
);
}
}

async resetTransport(mainWindow: EventEmitter) {
await TransportNodeHid.disconnect();
const transport = await TransportNodeHid.open();
transport.setAllowAutoDisconnect(false);
TransportNodeHid.disconnect();
const transport = await openTransport();
this.concordiumClient = new ConcordiumLedgerClientMain(
mainWindow,
transport
Expand All @@ -58,6 +109,62 @@ export default class LedgerObserverImpl implements LedgerObserver {
}
}

private async updateLedgerState(
mainWindow: EventEmitter,
deviceName?: string
) {
let appAndVersion;
try {
const transport = await openTransport();
this.concordiumClient = new ConcordiumLedgerClientMain(
mainWindow,
transport
);
appAndVersion = await this.concordiumClient.getAppAndVersion();
} catch {
mainWindow.emit(
ledgerIpcCommands.listenChannel,
LedgerSubscriptionAction.RESET,
deviceName
);
return;
}

let action;
if (!appAndVersion) {
// We could not extract the version information.
action = LedgerSubscriptionAction.RESET;
} else if (!isConcordiumApp(appAndVersion)) {
// The device has been connected, but the Concordium application has not
// been opened yet.
action = LedgerSubscriptionAction.PENDING;
} else if (isOutdated(appAndVersion)) {
// The device has been connected, but the Concordium application is outdated
action = LedgerSubscriptionAction.OUTDATED;
} else {
action = LedgerSubscriptionAction.CONNECTED_SUBSCRIPTION;
}
mainWindow.emit(ledgerIpcCommands.listenChannel, action, deviceName);
}

private async onAdd(
event: DescriptorEvent<string>,
mainWindow: EventEmitter
) {
const deviceName = event.deviceModel?.productName;
this.updateLedgerState(mainWindow, deviceName);
}

private onRemove(mainWindow: EventEmitter) {
if (this.concordiumClient) {
this.concordiumClient.closeTransport();
}
mainWindow.emit(
ledgerIpcCommands.listenChannel,
LedgerSubscriptionAction.RESET
);
}

/**
* Creates an observer for events happening on the Ledger. The events will be sent using
* IPC to the window provided.
Expand All @@ -76,50 +183,11 @@ export default class LedgerObserverImpl implements LedgerObserver {
LedgerSubscriptionAction.ERROR_SUBSCRIPTION
);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
next: async (event: any) => {
next: async (event: DescriptorEvent<string>) => {
if (event.type === 'add') {
const deviceName = event.deviceModel.productName;
const transport = await TransportNodeHid.open();
transport.setAllowAutoDisconnect(false);
this.concordiumClient = new ConcordiumLedgerClientMain(
mainWindow,
transport
);
let appAndVersion;
try {
appAndVersion = await this.concordiumClient.getAppAndVersion();
} catch (e) {
throw new Error(`Unable to get current app: ${e}`);
}
let action;
if (!appAndVersion) {
// We could not extract the version information.
action = LedgerSubscriptionAction.RESET;
} else if (!isConcordiumApp(appAndVersion)) {
// The device has been connected, but the Concordium application has not
// been opened yet.
action = LedgerSubscriptionAction.PENDING;
} else if (isOutdated(appAndVersion)) {
// The device has been connected, but the Concordium application is outdated
action = LedgerSubscriptionAction.OUTDATED;
} else {
action =
LedgerSubscriptionAction.CONNECTED_SUBSCRIPTION;
}
mainWindow.emit(
ledgerIpcCommands.listenChannel,
action,
deviceName
);
this.onAdd(event, mainWindow);
} else if (event.type === 'remove') {
if (this.concordiumClient) {
this.concordiumClient.closeTransport();
}
mainWindow.emit(
ledgerIpcCommands.listenChannel,
LedgerSubscriptionAction.RESET
);
this.onRemove(mainWindow);
}
},
};
Expand Down
108 changes: 54 additions & 54 deletions app/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,59 +10,59 @@
"@mapbox/node-pre-gyp" "^1.0.0"
node-addon-api "^3.0.0"

"@ledgerhq/devices@^8.0.4":
version "8.0.4"
resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.0.4.tgz#ebc7779adbbec2d046424603a481623eb3fbe306"
integrity sha512-dxOiWZmtEv1tgw70+rW8gviCRZUeGDUnxY6HUPiRqTAc0Ts2AXxiJChgAsPvIywWTGW+S67Nxq1oTZdpRbdt+A==
dependencies:
"@ledgerhq/errors" "^6.12.7"
"@ledgerhq/logs" "^6.10.1"
rxjs "6"
"@ledgerhq/devices@^8.2.1":
version "8.2.1"
resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.2.1.tgz#c59eec50bacd2f962e22c723a74600160d41fbd0"
integrity sha512-l/2I/Xzt7Z32OmGzoc/mUvaZivdn7Id/SO7hBTGpk7PtJTpBRxVAabP4GWEKCayGyOAcvTwoVxM0HMkNVfIzOQ==
dependencies:
"@ledgerhq/errors" "^6.16.2"
"@ledgerhq/logs" "^6.12.0"
rxjs "^7.8.1"
semver "^7.3.5"

"@ledgerhq/errors@^6.12.7":
version "6.12.7"
resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.12.7.tgz#c7b630488d5713bc7b1e1682d6ab5d08918c69f1"
integrity sha512-1BpjzFErPK7qPFx0oItcX0mNLJMplVAm2Dpl5urZlubewnTyyw5sahIBjU+8LLCWJ2eGEh/0wyvh0jMtR0n2Mg==
"@ledgerhq/errors@^6.16.2":
version "6.16.2"
resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.16.2.tgz#cf3939dc92ed0871aa7fdd45e6d13446a1eaeccd"
integrity sha512-jFpohaSW+p1Obp3NDT9QSByEtT3gtBZIjVNu8m25gnrH5zdtfPVlPwH6UiuS50s+2dHQyehV8hF+IfreKDWAZA==

"@ledgerhq/hw-transport-node-hid-noevents@^6.27.16":
version "6.27.16"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-6.27.16.tgz#5497b8194ec7802e221a0dd95b799d6c12da1a89"
integrity sha512-7MK245Cfj4B4WijO+a0iux+glmyHr44ezSuec4h1PfQrtiOGf+Sk26oi8WHJX+pabc9c566Qxkgdo4FvHWCCaA==
"@ledgerhq/hw-transport-node-hid-noevents@^6.29.4":
version "6.29.4"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-6.29.4.tgz#9fae3a47e61ebf39448b700bfa087c9f3a5df7e7"
integrity sha512-g19nEwES/SIG9z0Zah6Y2zf883JCT57HSlHgR5DRHPrNXIJbJRZV/UbHQo3az/wZ642WmzGqkPUdmYGDjQu4rg==
dependencies:
"@ledgerhq/devices" "^8.0.4"
"@ledgerhq/errors" "^6.12.7"
"@ledgerhq/hw-transport" "^6.28.5"
"@ledgerhq/logs" "^6.10.1"
"@ledgerhq/devices" "^8.2.1"
"@ledgerhq/errors" "^6.16.2"
"@ledgerhq/hw-transport" "^6.30.4"
"@ledgerhq/logs" "^6.12.0"
node-hid "^2.1.2"

"@ledgerhq/hw-transport-node-hid-singleton@^6.28.14":
version "6.28.14"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-singleton/-/hw-transport-node-hid-singleton-6.28.14.tgz#1d55366e2daf425fe061067378c1124c52faf2cd"
integrity sha512-M7xhc9K5UCfOBkNx1Z8qyH6Z72t0gwQ+DSbjTv6+pyVqNYwo6EewQSTiJ4YukfHKRm211ElGynHaSH6S883mCA==
dependencies:
"@ledgerhq/devices" "^8.0.4"
"@ledgerhq/errors" "^6.12.7"
"@ledgerhq/hw-transport" "^6.28.5"
"@ledgerhq/hw-transport-node-hid-noevents" "^6.27.16"
"@ledgerhq/logs" "^6.10.1"
lodash "^4.17.21"
version "6.30.4"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-singleton/-/hw-transport-node-hid-singleton-6.30.4.tgz#f66653529c4231ef84ff6dacdd4a2434f218a6b6"
integrity sha512-YQH0tlk82GdmyHjIusRjJfRTGHnW2C23eik7Qk8w3ADLoR/Y9S2Q7uhS3t3494Raw2fbQ9ebd9c7S5mkAUt/8Q==
dependencies:
"@ledgerhq/devices" "^8.2.1"
"@ledgerhq/errors" "^6.16.2"
"@ledgerhq/hw-transport" "^6.30.4"
"@ledgerhq/hw-transport-node-hid-noevents" "^6.29.4"
"@ledgerhq/logs" "^6.12.0"
node-hid "^2.1.2"
usb "^2.9.0"
usb "2.9.0"

"@ledgerhq/hw-transport@^6.28.5":
version "6.28.5"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.28.5.tgz#675193be2f695a596068145351da598316c25831"
integrity sha512-xmw5RhYbqExBBqTvOnOjN/RYNIGMBxFJ+zcYNfkfw/E+uEY3L7xq8Z7sC/n7URTT6xtEctElqduBJnBQE4OQtw==
"@ledgerhq/hw-transport@^6.30.4":
version "6.30.4"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.30.4.tgz#05dee1e9cdec0e430594ecf7f5cf90c13721ad70"
integrity sha512-VBcVd7UG8HDrjWMoZI5rqBDz+PBxLHTIPZOGY/fdMoEUwaBbss0Z3MxuJanMyerlfaLqnBSVuL0blz7rOyagkw==
dependencies:
"@ledgerhq/devices" "^8.0.4"
"@ledgerhq/errors" "^6.12.7"
"@ledgerhq/devices" "^8.2.1"
"@ledgerhq/errors" "^6.16.2"
"@ledgerhq/logs" "^6.12.0"
events "^3.3.0"

"@ledgerhq/logs@^6.10.1":
version "6.10.1"
resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.10.1.tgz#5bd16082261d7364eabb511c788f00937dac588d"
integrity sha512-z+ILK8Q3y+nfUl43ctCPuR4Y2bIxk/ooCQFwZxhtci1EhAtMDzMAx2W25qx8G1PPL9UUOdnUax19+F0OjXoj4w==
"@ledgerhq/logs@^6.12.0":
version "6.12.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.12.0.tgz#ad903528bf3687a44da435d7b2479d724d374f5d"
integrity sha512-ExDoj1QV5eC6TEbMdLUMMk9cfvNKhhv5gXol4SmULRVCx/3iyCPhJ74nsb3S0Vb+/f+XujBEj3vQn5+cwS0fNA==

"@mapbox/node-pre-gyp@^1.0.0":
version "1.0.5"
Expand All @@ -80,9 +80,9 @@
tar "^6.1.0"

"@types/ledgerhq__hw-transport-node-hid@^4.22.2":
version "4.22.2"
resolved "https://registry.yarnpkg.com/@types/ledgerhq__hw-transport-node-hid/-/ledgerhq__hw-transport-node-hid-4.22.2.tgz#f3de58b9b49b461dd96e5bfb646328c242e70aca"
integrity sha512-b9dXYEZ8Iijv20LykEVhbThVzSjchMpvj8y/mSsCSP1n7Ncs+YqD8mjEV/bGTks9jAP8CdxhRqOGq+h47/4B9g==
version "4.22.5"
resolved "https://registry.yarnpkg.com/@types/ledgerhq__hw-transport-node-hid/-/ledgerhq__hw-transport-node-hid-4.22.5.tgz#67f203e6fdcc77a5ddbd4f36e90fc219573833ab"
integrity sha512-a9d88sFWpjPu6J1F3JYL2nzSzzZLLSD0E8bnU9daKhBqomVswpXsoU/lfrnSSt2pVMgiRiZEKIMvW5ih2vF7sQ==
dependencies:
"@types/ledgerhq__hw-transport" "*"
"@types/node" "*"
Expand Down Expand Up @@ -1135,12 +1135,12 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"

rxjs@6:
version "6.6.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
rxjs@^7.8.1:
version "7.8.1"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
dependencies:
tslib "^1.9.0"
tslib "^2.1.0"

safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
version "5.2.1"
Expand Down Expand Up @@ -1356,10 +1356,10 @@ tough-cookie@~2.5.0:
psl "^1.1.28"
punycode "^2.1.1"

tslib@^1.9.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.1.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==

tunnel-agent@^0.6.0:
version "0.6.0"
Expand All @@ -1380,7 +1380,7 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"

usb@^2.9.0:
[email protected]:
version "2.9.0"
resolved "https://registry.yarnpkg.com/usb/-/usb-2.9.0.tgz#8ae3b175f93bee559400bff33491eee63406b6a2"
integrity sha512-G0I/fPgfHUzWH8xo2KkDxTTFruUWfppgSFJ+bQxz/kVY2x15EQ/XDB7dqD1G432G4gBG4jYQuF3U7j/orSs5nw==
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@
"dependencies": {
"@concordium/web-sdk": "^6.4.0",
"@hot-loader/react-dom": "^16.13.0",
"@ledgerhq/devices": "^8.2.1",
"@protobuf-ts/grpc-transport": "2.8.2",
"@reduxjs/toolkit": "^1.4.0",
"archiver": "^5.3.0",
Expand Down
Loading

0 comments on commit 3f57f0e

Please sign in to comment.