Skip to content

Commit

Permalink
[DDW-722] Fix transaction issue
Browse files Browse the repository at this point in the history
  • Loading branch information
renanvalentin committed Apr 4, 2022
1 parent f0370af commit 2b2fd73
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 216 deletions.
38 changes: 17 additions & 21 deletions source/main/ipc/getHardwareWalletChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import TrezorConnect, {
} from 'trezor-connect';
import { find, get, includes, last, omit } from 'lodash';
import { derivePublic as deriveChildXpub } from 'cardano-crypto.js';
import { listenDevices } from './listenDevices';
import {
deviceDetection,
waitForDevice,
} from './hardwareWallets/ledger/deviceDetection';
import { IpcSender } from '../../common/ipc/lib/IpcChannel';
import { logger } from '../utils/logging';
import { HardwareWalletTransportDeviceRequest } from '../../common/types/hardware-wallets.types';
Expand Down Expand Up @@ -270,7 +273,7 @@ export const handleHardwareWalletRequests = async (
try {
// @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
logger.info('[HW-DEBUG] getHardwareWalletTransportChannel:: LEDGER');
let transportList = await TransportNodeHid.list();
const transportList = await TransportNodeHid.list();
let hw;
let lastConnectedPath;
// @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
Expand All @@ -286,27 +289,19 @@ export const handleHardwareWalletRequests = async (
try {
// @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
logger.info('[HW-DEBUG] INIT NEW transport');
hw = await TransportNodeHid.create();
transportList = await TransportNodeHid.list();
lastConnectedPath = last(transportList);
// @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
logger.info(
`[HW-DEBUG] getHardwareWalletTransportChannel::lastConnectedPath=${JSON.stringify(
lastConnectedPath
)}`
);
const deviceList = getDevices();
// @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
logger.info(
`[HW-DEBUG] getHardwareWalletTransportChannel::deviceList=${JSON.stringify(
deviceList
)}`
);
const device = find(deviceList, ['path', lastConnectedPath]);

const { device } = await waitForDevice();
if (devicesMemo[device.path]) {
logger.info('[HW-DEBUG] CLOSING EXISTING TRANSPORT');
await devicesMemo[device.path].transport.close();
}
const transport = await TransportNodeHid.open(device.path);
hw = transport;
lastConnectedPath = device.path;
// @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
logger.info('[HW-DEBUG] INIT NEW transport - DONE');
// @ts-ignore
deviceConnection = new AppAda(hw);
deviceConnection = new AppAda(transport);
devicesMemo[lastConnectedPath] = {
device,
transport: hw,
Expand Down Expand Up @@ -414,7 +409,7 @@ export const handleHardwareWalletRequests = async (
observer.next(payload);
};

listenDevices(onAdd, onRemove);
deviceDetection(onAdd, onRemove);

logger.info('[HW-DEBUG] OBSERVER INIT - listener started');
} catch (e) {
Expand Down Expand Up @@ -728,6 +723,7 @@ export const handleHardwareWalletRequests = async (
deviceId: deviceSerial.serial,
});
} catch (error) {
logger.info('[HW-DEBUG] EXPORT KEY ERROR', error);
throw error;
}
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid-noevents';

import { logger } from '../../../../utils/logging';
import { DeviceTracker } from './deviceTracker';
import { detectDevices as useEventDrivenDetection } from './eventDrivenDetection';
import { detectDevices as usePollingDrivenDetection } from './pollingDrivenDetection';
import { Detector, TrackedDevice, DectorUnsubscriber } from './types';

type Payload = {
type: 'add' | 'remove';
} & TrackedDevice;

export const deviceDetection = (
onAdd: (arg0: Payload) => void,
onRemove: (arg0: Payload) => void
) => {
Promise.resolve(DeviceTracker.getDevices()).then((devices) => {
// this needs to run asynchronously so the subscription is defined during this phase
for (const device of devices) {
onAdd({
type: 'add',
...DeviceTracker.getTrackedDeviceByPath(device.path),
});
}
});

const handleOnAdd = (trackedDevice: TrackedDevice) =>
onAdd({ type: 'add', ...trackedDevice });
const handleOnRemove = (trackedDevice: TrackedDevice) =>
onRemove({ type: 'remove', ...trackedDevice });

let detectDevices: Detector;

if (TransportNodeHid.isSupported()) {
logger.info('[HW-DEBUG] Using usb-detection');

detectDevices = useEventDrivenDetection;
} else {
logger.info('[HW-DEBUG] Using polling');

detectDevices = usePollingDrivenDetection;
}

detectDevices(handleOnAdd, handleOnRemove);
};

export const waitForDevice = () => {
return new Promise<TrackedDevice>(async (resolve) => {
const currentDevices = await DeviceTracker.getDevices();

for (const device of currentDevices) {
return resolve(DeviceTracker.getTrackedDeviceByPath(device.path));
}

let detectDevices: Detector;
let unsubscribe: DectorUnsubscriber = null;

if (TransportNodeHid.isSupported()) {
logger.info('[HW-DEBUG] Using usb-detection');

detectDevices = useEventDrivenDetection;
} else {
logger.info('[HW-DEBUG] Using polling');

detectDevices = usePollingDrivenDetection;
}

const handleOnAdd = (trackedDevice: TrackedDevice) => {
if (unsubscribe) {
unsubscribe();
}

return resolve(trackedDevice);
};

const handleOnRemove = () => false;

unsubscribe = detectDevices(handleOnAdd, handleOnRemove);
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { getDevices } from '@ledgerhq/hw-transport-node-hid-noevents';
import { identifyUSBProductId } from '@ledgerhq/devices';

import { logger } from '../../../../utils/logging';
import { Device, TrackedDevice } from './types';

export class DeviceTracker {
knownDevices: Map<string, TrackedDevice>;

static getUniqueDevices() {
return [...new Set<string>(getDevices().map((d: Device) => d.path))];
}

static getDeviceByPath(path: string): Device {
return getDevices().find((d: Device) => d.path === path);
}

static getTrackedDeviceByPath(path: string) {
const device = DeviceTracker.getDeviceByPath(path);

const descriptor: string = device.path;
const deviceModel = (identifyUSBProductId(
device.productId
) as unknown) as string;

return { device, deviceModel, descriptor } as TrackedDevice;
}

static getDevices() {
return getDevices();
}

constructor() {
this.knownDevices = new Map();

getDevices().forEach((d) => this.knownDevices.set(d.path, d));
}

findNewDevice() {
const currentDevices = DeviceTracker.getUniqueDevices();
const [newDevicePath] = currentDevices.filter(
(d) => !this.knownDevices.has(d)
);
const knownDevicesPath = Array.from(this.knownDevices.keys());

if (newDevicePath) {
const newDevice = DeviceTracker.getTrackedDeviceByPath(newDevicePath);
this.knownDevices.set(newDevicePath, newDevice);

logger.info('[HW-DEBUG] DeviceTracker - New device found:', {
newDevicePath,
currentDevices,
knownDevicesPath,
});

return newDevice;
}

logger.info('[HW-DEBUG] DeviceTracker - No new device found:', {
currentDevices,
knownDevicesPath,
});

return null;
}

findRemovedDevice() {
const currentDevices = DeviceTracker.getUniqueDevices();
const [removedDevicePath] = Array.from(this.knownDevices.keys())
.filter((d) => !currentDevices.includes(d))
.map((d) => d);
const knownDevicesPath = Array.from(this.knownDevices.keys());

if (removedDevicePath) {
const removedDevice = this.knownDevices.get(removedDevicePath);
this.knownDevices.delete(removedDevicePath);

logger.info('[HW-DEBUG] DeviceTracker - Removed device found:', {
removedDevicePath,
removedDevice,
currentDevices,
knownDevicesPath,
});

return removedDevice;
}

logger.info('[HW-DEBUG] DeviceTracker - No removed device found:', {
currentDevices,
knownDevicesPath,
});

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { ledgerUSBVendorId } from '@ledgerhq/devices';
import usbDetect from 'usb-detection';

import { logger } from '../../../../utils/logging';
import { DeviceTracker } from './deviceTracker';
import { Detector } from './types';

const deviceToLog = ({ productId, locationId, deviceAddress }) =>
`productId=${productId} locationId=${locationId} deviceAddress=${deviceAddress}`;

let isMonitoring = false;

const monitorUSBDevices = () => {
if (!isMonitoring) {
isMonitoring = true;
usbDetect.startMonitoring();
}
};

const stopMonitoring = () => {
if (isMonitoring) {
// redeem the monitoring so the process can be terminated.
usbDetect.stopMonitoring();
}
};

// No better way for now. see https://github.com/LedgerHQ/ledgerjs/issues/434
process.on('exit', () => {
stopMonitoring();
});

const addEvent = `add:${ledgerUSBVendorId}`;
const removeEvent = `remove:${ledgerUSBVendorId}`;

export const detectDevices: Detector = (onAdd, onRemove) => {
let timeout;

monitorUSBDevices();

const deviceTracker = new DeviceTracker();

const add = (device: usbDetect.Device) => {
logger.info(
`[HW-DEBUG] USB-DETECTION ADDED DEVICE: ${deviceToLog(device)}`
);

if (!timeout) {
// a time is needed for the device to actually be connectable over HID..
// we also take this time to not emit the device yet and potentially cancel it if a remove happens.
timeout = setTimeout(() => {
const newDevice = deviceTracker.findNewDevice();

if (newDevice) {
onAdd(newDevice);
}

timeout = null;
}, 1500);
}
};

const remove = (device: usbDetect.Device) => {
logger.info(
`[HW-DEBUG] USB-DETECTION REMOVED DEVICE: ${deviceToLog(device)}`
);

if (timeout) {
clearTimeout(timeout);
timeout = null;
} else {
const removedDevice = deviceTracker.findNewDevice();

if (removedDevice) {
onRemove(removedDevice);
}
}
};

usbDetect.on(addEvent, add);
usbDetect.on(removeEvent, remove);

return () => {
if (timeout) clearTimeout(timeout);
// @ts-expect-error not all EventEmitter methods are covered in its definition file
usbDetect.off(addEvent, add);
// @ts-expect-error not all EventEmitter methods are covered in its definition file
usbDetect.off(removeEvent, remove);
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { deviceDetection, waitForDevice } from './deviceDetection';
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { logger } from '../../../../utils/logging';
import { DeviceTracker } from './deviceTracker';
import { Detector } from './types';

export const detectDevices: Detector = (onAdd, onRemove) => {
let timer;

const stopPolling = () => {
if (timer) {
clearInterval(timer);
}
};

process.on('exit', () => {
stopPolling();
});

const deviceTracker = new DeviceTracker();

const runPolling = () => {
logger.info('[HW-DEBUG] Polling devices');
const newDevice = deviceTracker.findNewDevice();

if (newDevice) {
onAdd(newDevice);
}

const removedDevice = deviceTracker.findNewDevice();

if (removedDevice) {
onRemove(removedDevice);
}
};

timer = setInterval(runPolling, 1000);

return () => {
stopPolling();
};
};

0 comments on commit 2b2fd73

Please sign in to comment.