@onekeyfe/hwk-ledger-connector-electron-ble 1.2.3-alpha.1 → 1.2.3-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +25 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.js +43 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +37 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LedgerConnectorBase } from '@onekeyfe/hwk-ledger-adapter';
|
|
2
|
-
import { ElectronBleApi } from '@onekeyfe/hwk-adapter-core';
|
|
2
|
+
import { ElectronBleApi, ElectronBleConnectOptions, ElectronBleMatch } from '@onekeyfe/hwk-adapter-core';
|
|
3
3
|
export { ElectronBleApi, ElectronBleDeviceInfo } from '@onekeyfe/hwk-adapter-core';
|
|
4
4
|
import * as rxjs from 'rxjs';
|
|
5
5
|
import { Transport, TransportDiscoveredDevice } from '@ledgerhq/device-management-kit';
|
|
@@ -24,4 +24,27 @@ declare class LedgerElectronBleTransport implements Transport {
|
|
|
24
24
|
disconnect({ connectedDevice, }: Parameters<Transport['disconnect']>[0]): ReturnType<Transport['disconnect']>;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
declare const LEDGER_BLE_VENDOR = "ledger";
|
|
28
|
+
/**
|
|
29
|
+
* Frame size bounds for the 0x08 handshake, kept together because they are two
|
|
30
|
+
* halves of one rule: the device reports the size it can take in a single
|
|
31
|
+
* byte, so 255 is the most it can ask for, and 20 is the floor we raise a
|
|
32
|
+
* smaller answer to. The negotiated value in between is used as-is — capping
|
|
33
|
+
* it would negotiate and then discard the answer, leaving every transfer at
|
|
34
|
+
* roughly 7x the frame count.
|
|
35
|
+
*/
|
|
36
|
+
declare const LEDGER_BLE_MIN_FRAME_SIZE = 20;
|
|
37
|
+
declare const LEDGER_BLE_MAX_FRAME_SIZE = 255;
|
|
38
|
+
/**
|
|
39
|
+
* Ledger GATT uuids are per device model, so unlike Trezor's fixed profile
|
|
40
|
+
* they are looked up from the DMK data source at discovery time and passed in
|
|
41
|
+
* here. Only the vendor key and the framing rule are constant.
|
|
42
|
+
*/
|
|
43
|
+
declare function ledgerBleMatch(serviceUuids: string[]): ElectronBleMatch;
|
|
44
|
+
declare function ledgerBleConnectProfile(gatt: {
|
|
45
|
+
serviceUuid: string;
|
|
46
|
+
writeUuid: string;
|
|
47
|
+
notifyUuid: string;
|
|
48
|
+
}): ElectronBleConnectOptions;
|
|
49
|
+
|
|
50
|
+
export { LEDGER_BLE_MAX_FRAME_SIZE, LEDGER_BLE_MIN_FRAME_SIZE, LEDGER_BLE_VENDOR, LedgerElectronBleTransport, createLedgerElectronBleConnector, ledgerBleConnectProfile, ledgerBleMatch };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LedgerConnectorBase } from '@onekeyfe/hwk-ledger-adapter';
|
|
2
|
-
import { ElectronBleApi } from '@onekeyfe/hwk-adapter-core';
|
|
2
|
+
import { ElectronBleApi, ElectronBleConnectOptions, ElectronBleMatch } from '@onekeyfe/hwk-adapter-core';
|
|
3
3
|
export { ElectronBleApi, ElectronBleDeviceInfo } from '@onekeyfe/hwk-adapter-core';
|
|
4
4
|
import * as rxjs from 'rxjs';
|
|
5
5
|
import { Transport, TransportDiscoveredDevice } from '@ledgerhq/device-management-kit';
|
|
@@ -24,4 +24,27 @@ declare class LedgerElectronBleTransport implements Transport {
|
|
|
24
24
|
disconnect({ connectedDevice, }: Parameters<Transport['disconnect']>[0]): ReturnType<Transport['disconnect']>;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
declare const LEDGER_BLE_VENDOR = "ledger";
|
|
28
|
+
/**
|
|
29
|
+
* Frame size bounds for the 0x08 handshake, kept together because they are two
|
|
30
|
+
* halves of one rule: the device reports the size it can take in a single
|
|
31
|
+
* byte, so 255 is the most it can ask for, and 20 is the floor we raise a
|
|
32
|
+
* smaller answer to. The negotiated value in between is used as-is — capping
|
|
33
|
+
* it would negotiate and then discard the answer, leaving every transfer at
|
|
34
|
+
* roughly 7x the frame count.
|
|
35
|
+
*/
|
|
36
|
+
declare const LEDGER_BLE_MIN_FRAME_SIZE = 20;
|
|
37
|
+
declare const LEDGER_BLE_MAX_FRAME_SIZE = 255;
|
|
38
|
+
/**
|
|
39
|
+
* Ledger GATT uuids are per device model, so unlike Trezor's fixed profile
|
|
40
|
+
* they are looked up from the DMK data source at discovery time and passed in
|
|
41
|
+
* here. Only the vendor key and the framing rule are constant.
|
|
42
|
+
*/
|
|
43
|
+
declare function ledgerBleMatch(serviceUuids: string[]): ElectronBleMatch;
|
|
44
|
+
declare function ledgerBleConnectProfile(gatt: {
|
|
45
|
+
serviceUuid: string;
|
|
46
|
+
writeUuid: string;
|
|
47
|
+
notifyUuid: string;
|
|
48
|
+
}): ElectronBleConnectOptions;
|
|
49
|
+
|
|
50
|
+
export { LEDGER_BLE_MAX_FRAME_SIZE, LEDGER_BLE_MIN_FRAME_SIZE, LEDGER_BLE_VENDOR, LedgerElectronBleTransport, createLedgerElectronBleConnector, ledgerBleConnectProfile, ledgerBleMatch };
|
package/dist/index.js
CHANGED
|
@@ -20,8 +20,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
LEDGER_BLE_MAX_FRAME_SIZE: () => LEDGER_BLE_MAX_FRAME_SIZE,
|
|
24
|
+
LEDGER_BLE_MIN_FRAME_SIZE: () => LEDGER_BLE_MIN_FRAME_SIZE,
|
|
25
|
+
LEDGER_BLE_VENDOR: () => LEDGER_BLE_VENDOR,
|
|
23
26
|
LedgerElectronBleTransport: () => LedgerElectronBleTransport,
|
|
24
|
-
createLedgerElectronBleConnector: () => createLedgerElectronBleConnector
|
|
27
|
+
createLedgerElectronBleConnector: () => createLedgerElectronBleConnector,
|
|
28
|
+
ledgerBleConnectProfile: () => ledgerBleConnectProfile,
|
|
29
|
+
ledgerBleMatch: () => ledgerBleMatch
|
|
25
30
|
});
|
|
26
31
|
module.exports = __toCommonJS(index_exports);
|
|
27
32
|
|
|
@@ -32,9 +37,26 @@ var import_hwk_ledger_adapter = require("@onekeyfe/hwk-ledger-adapter");
|
|
|
32
37
|
var import_device_management_kit = require("@ledgerhq/device-management-kit");
|
|
33
38
|
var import_purify_ts = require("purify-ts");
|
|
34
39
|
var import_rxjs = require("rxjs");
|
|
40
|
+
|
|
41
|
+
// src/bleProfile.ts
|
|
42
|
+
var LEDGER_BLE_VENDOR = "ledger";
|
|
43
|
+
var LEDGER_BLE_MIN_FRAME_SIZE = 20;
|
|
44
|
+
var LEDGER_BLE_MAX_FRAME_SIZE = 255;
|
|
45
|
+
function ledgerBleMatch(serviceUuids) {
|
|
46
|
+
return { serviceUuids };
|
|
47
|
+
}
|
|
48
|
+
function ledgerBleConnectProfile(gatt) {
|
|
49
|
+
return {
|
|
50
|
+
vendor: LEDGER_BLE_VENDOR,
|
|
51
|
+
...gatt,
|
|
52
|
+
// Ledger frames carry their own length and sequence; padding corrupts them.
|
|
53
|
+
write: { mode: "raw", maxLength: LEDGER_BLE_MAX_FRAME_SIZE }
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/LedgerElectronBleTransport.ts
|
|
35
58
|
var import_hwk_adapter_core = require("@onekeyfe/hwk-adapter-core");
|
|
36
59
|
var TRANSPORT_ID = "ELECTRON_BLE";
|
|
37
|
-
var FRAME_SIZE = 20;
|
|
38
60
|
var normalizeUuid = (uuid) => uuid.replace(/-/g, "").toLowerCase();
|
|
39
61
|
var toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
40
62
|
var LedgerElectronBleTransport = class {
|
|
@@ -74,8 +96,8 @@ var LedgerElectronBleTransport = class {
|
|
|
74
96
|
const availability = await this.bridge.checkAvailability();
|
|
75
97
|
if (!availability.available) throw new Error(`Bluetooth unavailable: ${availability.state}`);
|
|
76
98
|
const infos = await this.bridge.scan({
|
|
77
|
-
vendor:
|
|
78
|
-
|
|
99
|
+
vendor: LEDGER_BLE_VENDOR,
|
|
100
|
+
match: ledgerBleMatch(this.args.deviceModelDataSource.getBluetoothServices())
|
|
79
101
|
});
|
|
80
102
|
const devices = [];
|
|
81
103
|
for (const info of infos) {
|
|
@@ -141,12 +163,14 @@ var LedgerElectronBleTransport = class {
|
|
|
141
163
|
release();
|
|
142
164
|
return (0, import_purify_ts.Left)(new import_device_management_kit.UnknownDeviceError());
|
|
143
165
|
}
|
|
144
|
-
await this.bridge.connect(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
166
|
+
await this.bridge.connect(
|
|
167
|
+
deviceId,
|
|
168
|
+
ledgerBleConnectProfile({
|
|
169
|
+
serviceUuid: known.profile.serviceUuid,
|
|
170
|
+
writeUuid: known.profile.writeUuid,
|
|
171
|
+
notifyUuid: known.profile.notifyUuid
|
|
172
|
+
})
|
|
173
|
+
);
|
|
150
174
|
removeDisconnect = this.bridge.onDeviceDisconnected((id) => {
|
|
151
175
|
if (id !== deviceId) return;
|
|
152
176
|
if (close()) {
|
|
@@ -193,7 +217,9 @@ var LedgerElectronBleTransport = class {
|
|
|
193
217
|
(bytes, resolve, reject) => {
|
|
194
218
|
if (bytes.length < 6 || bytes[0] !== 8 || bytes[5] < 6) {
|
|
195
219
|
reject(new Error("Invalid Ledger BLE MTU response"));
|
|
196
|
-
} else
|
|
220
|
+
} else {
|
|
221
|
+
resolve(Math.max(LEDGER_BLE_MIN_FRAME_SIZE, bytes[5]));
|
|
222
|
+
}
|
|
197
223
|
},
|
|
198
224
|
3e4
|
|
199
225
|
);
|
|
@@ -263,7 +289,12 @@ function createLedgerElectronBleConnector(bridge) {
|
|
|
263
289
|
}
|
|
264
290
|
// Annotate the CommonJS export names for ESM import in node:
|
|
265
291
|
0 && (module.exports = {
|
|
292
|
+
LEDGER_BLE_MAX_FRAME_SIZE,
|
|
293
|
+
LEDGER_BLE_MIN_FRAME_SIZE,
|
|
294
|
+
LEDGER_BLE_VENDOR,
|
|
266
295
|
LedgerElectronBleTransport,
|
|
267
|
-
createLedgerElectronBleConnector
|
|
296
|
+
createLedgerElectronBleConnector,
|
|
297
|
+
ledgerBleConnectProfile,
|
|
298
|
+
ledgerBleMatch
|
|
268
299
|
});
|
|
269
300
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/LedgerElectronBleConnector.ts","../src/LedgerElectronBleTransport.ts"],"sourcesContent":["export { createLedgerElectronBleConnector } from './LedgerElectronBleConnector';\nexport { LedgerElectronBleTransport } from './LedgerElectronBleTransport';\nexport type { ElectronBleApi, ElectronBleDeviceInfo } from '@onekeyfe/hwk-adapter-core';\n","import { LedgerConnectorBase } from '@onekeyfe/hwk-ledger-adapter';\nimport { LedgerElectronBleTransport } from './LedgerElectronBleTransport';\n\nimport type { TransportArgs } from '@ledgerhq/device-management-kit';\nimport type { ElectronBleApi } from '@onekeyfe/hwk-adapter-core';\n\nexport function createLedgerElectronBleConnector(bridge: ElectronBleApi): LedgerConnectorBase {\n return new LedgerConnectorBase(\n async () => (args: TransportArgs) => new LedgerElectronBleTransport(bridge, args),\n { connectionType: 'ble' }\n );\n}\n","import {\n DisconnectError,\n GeneralDmkError,\n OpeningConnectionError,\n SendApduConcurrencyError,\n SendApduTimeoutError,\n TransportConnectedDevice,\n UnknownDeviceError,\n} from '@ledgerhq/device-management-kit';\nimport { Left, Right } from 'purify-ts';\nimport { defer, from, mergeMap } from 'rxjs';\nimport { hexToBytes } from '@onekeyfe/hwk-adapter-core';\n\nimport type {\n BleDeviceInfos,\n SendApduResult,\n Transport,\n TransportArgs,\n TransportDiscoveredDevice,\n} from '@ledgerhq/device-management-kit';\nimport type { ElectronBleApi, ElectronBleDeviceInfo } from '@onekeyfe/hwk-adapter-core';\n\nconst TRANSPORT_ID = 'ELECTRON_BLE';\n// The ATT default is safe on both native backends; never split a Ledger frame in main.\nconst FRAME_SIZE = 20;\nconst normalizeUuid = (uuid: string) => uuid.replace(/-/g, '').toLowerCase();\nconst toHex = (bytes: Uint8Array) =>\n Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');\n\nexport class LedgerElectronBleTransport implements Transport {\n private readonly devices = new Map<\n string,\n { device: TransportDiscoveredDevice; profile: BleDeviceInfos }\n >();\n\n private readonly connections = new Map<\n string,\n {\n close: () => boolean;\n connectedDevice?: TransportConnectedDevice;\n }\n >();\n\n private scanPromise: Promise<TransportDiscoveredDevice[]> | undefined;\n\n private readonly bridge: ElectronBleApi;\n\n private readonly args: Pick<\n TransportArgs,\n 'deviceModelDataSource' | 'apduSenderServiceFactory' | 'apduReceiverServiceFactory'\n >;\n\n constructor(bridge: ElectronBleApi, args: LedgerElectronBleTransport['args']) {\n this.bridge = bridge;\n this.args = args;\n }\n\n getIdentifier() {\n return TRANSPORT_ID;\n }\n\n isSupported() {\n return true;\n }\n\n private remember(info: ElectronBleDeviceInfo): TransportDiscoveredDevice | undefined {\n const profiles = this.args.deviceModelDataSource.getBluetoothServicesInfos();\n const profile = Object.values(profiles).find(candidate =>\n info.advertisedServiceUuids?.some(\n uuid => normalizeUuid(uuid) === normalizeUuid(candidate.serviceUuid)\n )\n );\n if (!profile) return undefined;\n const device: TransportDiscoveredDevice = {\n id: info.id,\n name: info.name,\n rssi: info.rssi,\n deviceModel: profile.deviceModel,\n transport: TRANSPORT_ID,\n };\n this.devices.set(info.id, { device, profile });\n return device;\n }\n\n private discover(): Promise<TransportDiscoveredDevice[]> {\n if (this.scanPromise) return this.scanPromise;\n const scan = (async () => {\n const availability = await this.bridge.checkAvailability();\n if (!availability.available) throw new Error(`Bluetooth unavailable: ${availability.state}`);\n const infos = await this.bridge.scan({\n vendor: 'ledger',\n serviceUuids: this.args.deviceModelDataSource.getBluetoothServices(),\n });\n const devices: TransportDiscoveredDevice[] = [];\n for (const info of infos) {\n const device = this.remember(info);\n if (device) devices.push(device);\n }\n return devices;\n })();\n this.scanPromise = scan;\n void scan\n .finally(() => {\n if (this.scanPromise === scan) this.scanPromise = undefined;\n })\n .catch(() => undefined);\n return scan;\n }\n\n startDiscovering() {\n return defer(() => this.discover()).pipe(mergeMap(devices => from(devices)));\n }\n\n listenToAvailableDevices() {\n return defer(() => this.discover());\n }\n\n async stopDiscovering(): Promise<void> {\n await this.scanPromise?.catch(() => undefined);\n await this.bridge.stopScan();\n }\n\n async connect({\n deviceId,\n onDisconnect,\n }: Parameters<Transport['connect']>[0]): ReturnType<Transport['connect']> {\n if (this.connections.has(deviceId))\n return Left(new OpeningConnectionError('Device is already connected'));\n let removeNotification: (() => void) | undefined;\n let removeDisconnect: (() => void) | undefined;\n let closed = false;\n let rejectPending: ((error: unknown) => void) | undefined;\n let receive: ((bytes: Uint8Array) => void) | undefined;\n const close = () => {\n if (closed) return false;\n closed = true;\n removeNotification?.();\n removeDisconnect?.();\n rejectPending?.(new Error('Bluetooth connection ended'));\n return true;\n };\n const release = () => {\n if (this.connections.get(deviceId)?.close === close) this.connections.delete(deviceId);\n };\n const disconnect = async (notify: boolean) => {\n if (!close()) return;\n await this.bridge.disconnect(deviceId).catch(() => undefined);\n release();\n if (notify) onDisconnect(deviceId);\n };\n // Reserve before the first await so concurrent acquires cannot share native GATT state.\n this.connections.set(deviceId, { close });\n try {\n await this.stopDiscovering();\n if (!this.devices.has(deviceId)) {\n const info = await this.bridge.getDevice(deviceId);\n if (info) this.remember(info);\n }\n const known = this.devices.get(deviceId);\n if (!known) {\n close();\n release();\n return Left(new UnknownDeviceError());\n }\n await this.bridge.connect(deviceId, {\n vendor: 'ledger',\n serviceUuid: known.profile.serviceUuid,\n writeUuid: known.profile.writeUuid,\n notifyUuid: known.profile.notifyUuid,\n });\n removeDisconnect = this.bridge.onDeviceDisconnected(id => {\n if (id !== deviceId) return;\n if (close()) {\n release();\n onDisconnect(deviceId);\n }\n });\n removeNotification = this.bridge.onNotification((id, hex) => {\n if (id !== deviceId || closed) return;\n if (!/^(?:[0-9a-f]{2})+$/i.test(hex)) {\n rejectPending?.(new Error('Malformed Bluetooth notification'));\n return;\n }\n receive?.(hexToBytes(hex));\n });\n await this.bridge.subscribe(deviceId);\n\n const exchange = async <T>(\n frames: Uint8Array[],\n accept: (\n bytes: Uint8Array,\n resolve: (value: T) => void,\n reject: (error: unknown) => void\n ) => void,\n timeoutMs: number\n ): Promise<T> => {\n if (closed) throw new Error('Bluetooth connection ended');\n if (receive) throw new Error('Bluetooth exchange is busy');\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await new Promise<T>((resolve, reject) => {\n rejectPending = reject;\n receive = bytes => accept(bytes, resolve, reject);\n timer = setTimeout(\n () => reject(new SendApduTimeoutError('Bluetooth response timed out')),\n timeoutMs\n );\n void (async () => {\n for (const frame of frames) {\n if (closed) throw new Error('Bluetooth connection ended');\n await this.bridge.write(deviceId, toHex(frame));\n }\n })().catch(reject);\n });\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n receive = undefined;\n rejectPending = undefined;\n }\n };\n\n const frameSize = await exchange<number>(\n [Uint8Array.of(0x08, 0, 0, 0, 0)],\n (bytes, resolve, reject) => {\n if (bytes.length < 6 || bytes[0] !== 0x08 || bytes[5] < 6) {\n reject(new Error('Invalid Ledger BLE MTU response'));\n } else resolve(Math.min(FRAME_SIZE, bytes[5]));\n },\n 30_000\n );\n if (closed) throw new Error('Bluetooth connection ended');\n const sender = this.args.apduSenderServiceFactory({ frameSize });\n const connected = new TransportConnectedDevice({\n ...known.device,\n type: 'BLE',\n sendApdu: async (apdu, _triggersDisconnection, abortTimeout): Promise<SendApduResult> => {\n if (closed) return Left(new DisconnectError());\n if (receive) return Left(new SendApduConcurrencyError());\n const receiver = this.args.apduReceiverServiceFactory();\n try {\n const result = await exchange<SendApduResult>(\n sender.getFrames(apdu).map(frame => frame.getRawData()),\n (bytes, resolve) =>\n receiver.handleFrame(bytes).caseOf({\n Left: error => resolve(Left(error)),\n Right: response => {\n response.map(value => resolve(Right(value)));\n },\n }),\n abortTimeout ?? 120_000\n );\n if (result.isLeft()) {\n await disconnect(true);\n }\n return result;\n } catch (error) {\n await disconnect(true);\n return Left(error instanceof SendApduTimeoutError ? error : new GeneralDmkError(error));\n }\n },\n });\n const connection = this.connections.get(deviceId);\n if (connection?.close === close) connection.connectedDevice = connected;\n return Right(connected);\n } catch (error) {\n await disconnect(false);\n return Left(new OpeningConnectionError(error));\n }\n }\n\n async disconnect({\n connectedDevice,\n }: Parameters<Transport['disconnect']>[0]): ReturnType<Transport['disconnect']> {\n const connection = this.connections.get(connectedDevice.id);\n if (connection?.connectedDevice !== connectedDevice || !connection.close())\n return Right(undefined);\n try {\n await this.bridge.disconnect(connectedDevice.id);\n return Right(undefined);\n } catch (error) {\n return Left(new DisconnectError(error));\n } finally {\n if (this.connections.get(connectedDevice.id) === connection) {\n this.connections.delete(connectedDevice.id);\n }\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/LedgerElectronBleConnector.ts","../src/LedgerElectronBleTransport.ts","../src/bleProfile.ts"],"sourcesContent":["export { createLedgerElectronBleConnector } from './LedgerElectronBleConnector';\nexport { LedgerElectronBleTransport } from './LedgerElectronBleTransport';\nexport type { ElectronBleApi, ElectronBleDeviceInfo } from '@onekeyfe/hwk-adapter-core';\n\nexport {\n LEDGER_BLE_VENDOR,\n LEDGER_BLE_MIN_FRAME_SIZE,\n LEDGER_BLE_MAX_FRAME_SIZE,\n ledgerBleMatch,\n ledgerBleConnectProfile,\n} from './bleProfile';\n","import { LedgerConnectorBase } from '@onekeyfe/hwk-ledger-adapter';\nimport { LedgerElectronBleTransport } from './LedgerElectronBleTransport';\n\nimport type { TransportArgs } from '@ledgerhq/device-management-kit';\nimport type { ElectronBleApi } from '@onekeyfe/hwk-adapter-core';\n\nexport function createLedgerElectronBleConnector(bridge: ElectronBleApi): LedgerConnectorBase {\n return new LedgerConnectorBase(\n async () => (args: TransportArgs) => new LedgerElectronBleTransport(bridge, args),\n { connectionType: 'ble' }\n );\n}\n","import {\n DisconnectError,\n GeneralDmkError,\n OpeningConnectionError,\n SendApduConcurrencyError,\n SendApduTimeoutError,\n TransportConnectedDevice,\n UnknownDeviceError,\n} from '@ledgerhq/device-management-kit';\nimport { Left, Right } from 'purify-ts';\nimport { defer, from, mergeMap } from 'rxjs';\nimport {\n LEDGER_BLE_MIN_FRAME_SIZE,\n LEDGER_BLE_VENDOR,\n ledgerBleConnectProfile,\n ledgerBleMatch,\n} from './bleProfile';\nimport { hexToBytes } from '@onekeyfe/hwk-adapter-core';\n\nimport type {\n BleDeviceInfos,\n SendApduResult,\n Transport,\n TransportArgs,\n TransportDiscoveredDevice,\n} from '@ledgerhq/device-management-kit';\nimport type { ElectronBleApi, ElectronBleDeviceInfo } from '@onekeyfe/hwk-adapter-core';\n\nconst TRANSPORT_ID = 'ELECTRON_BLE';\n// Ledger's own transports open at the ATT default and then raise it from the\n// device's answer to the 0x08 handshake below. Keep the same floor so a device\n// that reports nothing useful still works.\nconst normalizeUuid = (uuid: string) => uuid.replace(/-/g, '').toLowerCase();\nconst toHex = (bytes: Uint8Array) =>\n Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');\n\nexport class LedgerElectronBleTransport implements Transport {\n private readonly devices = new Map<\n string,\n { device: TransportDiscoveredDevice; profile: BleDeviceInfos }\n >();\n\n private readonly connections = new Map<\n string,\n {\n close: () => boolean;\n connectedDevice?: TransportConnectedDevice;\n }\n >();\n\n private scanPromise: Promise<TransportDiscoveredDevice[]> | undefined;\n\n private readonly bridge: ElectronBleApi;\n\n private readonly args: Pick<\n TransportArgs,\n 'deviceModelDataSource' | 'apduSenderServiceFactory' | 'apduReceiverServiceFactory'\n >;\n\n constructor(bridge: ElectronBleApi, args: LedgerElectronBleTransport['args']) {\n this.bridge = bridge;\n this.args = args;\n }\n\n getIdentifier() {\n return TRANSPORT_ID;\n }\n\n isSupported() {\n return true;\n }\n\n private remember(info: ElectronBleDeviceInfo): TransportDiscoveredDevice | undefined {\n const profiles = this.args.deviceModelDataSource.getBluetoothServicesInfos();\n const profile = Object.values(profiles).find(candidate =>\n info.advertisedServiceUuids?.some(\n uuid => normalizeUuid(uuid) === normalizeUuid(candidate.serviceUuid)\n )\n );\n if (!profile) return undefined;\n const device: TransportDiscoveredDevice = {\n id: info.id,\n name: info.name,\n rssi: info.rssi,\n deviceModel: profile.deviceModel,\n transport: TRANSPORT_ID,\n };\n this.devices.set(info.id, { device, profile });\n return device;\n }\n\n private discover(): Promise<TransportDiscoveredDevice[]> {\n if (this.scanPromise) return this.scanPromise;\n const scan = (async () => {\n const availability = await this.bridge.checkAvailability();\n if (!availability.available) throw new Error(`Bluetooth unavailable: ${availability.state}`);\n const infos = await this.bridge.scan({\n vendor: LEDGER_BLE_VENDOR,\n match: ledgerBleMatch(this.args.deviceModelDataSource.getBluetoothServices()),\n });\n const devices: TransportDiscoveredDevice[] = [];\n for (const info of infos) {\n const device = this.remember(info);\n if (device) devices.push(device);\n }\n return devices;\n })();\n this.scanPromise = scan;\n void scan\n .finally(() => {\n if (this.scanPromise === scan) this.scanPromise = undefined;\n })\n .catch(() => undefined);\n return scan;\n }\n\n startDiscovering() {\n return defer(() => this.discover()).pipe(mergeMap(devices => from(devices)));\n }\n\n listenToAvailableDevices() {\n return defer(() => this.discover());\n }\n\n async stopDiscovering(): Promise<void> {\n await this.scanPromise?.catch(() => undefined);\n await this.bridge.stopScan();\n }\n\n async connect({\n deviceId,\n onDisconnect,\n }: Parameters<Transport['connect']>[0]): ReturnType<Transport['connect']> {\n if (this.connections.has(deviceId))\n return Left(new OpeningConnectionError('Device is already connected'));\n let removeNotification: (() => void) | undefined;\n let removeDisconnect: (() => void) | undefined;\n let closed = false;\n let rejectPending: ((error: unknown) => void) | undefined;\n let receive: ((bytes: Uint8Array) => void) | undefined;\n const close = () => {\n if (closed) return false;\n closed = true;\n removeNotification?.();\n removeDisconnect?.();\n rejectPending?.(new Error('Bluetooth connection ended'));\n return true;\n };\n const release = () => {\n if (this.connections.get(deviceId)?.close === close) this.connections.delete(deviceId);\n };\n const disconnect = async (notify: boolean) => {\n if (!close()) return;\n await this.bridge.disconnect(deviceId).catch(() => undefined);\n release();\n if (notify) onDisconnect(deviceId);\n };\n // Reserve before the first await so concurrent acquires cannot share native GATT state.\n this.connections.set(deviceId, { close });\n try {\n await this.stopDiscovering();\n if (!this.devices.has(deviceId)) {\n const info = await this.bridge.getDevice(deviceId);\n if (info) this.remember(info);\n }\n const known = this.devices.get(deviceId);\n if (!known) {\n close();\n release();\n return Left(new UnknownDeviceError());\n }\n await this.bridge.connect(\n deviceId,\n ledgerBleConnectProfile({\n serviceUuid: known.profile.serviceUuid,\n writeUuid: known.profile.writeUuid,\n notifyUuid: known.profile.notifyUuid,\n })\n );\n removeDisconnect = this.bridge.onDeviceDisconnected(id => {\n if (id !== deviceId) return;\n if (close()) {\n release();\n onDisconnect(deviceId);\n }\n });\n removeNotification = this.bridge.onNotification((id, hex) => {\n if (id !== deviceId || closed) return;\n if (!/^(?:[0-9a-f]{2})+$/i.test(hex)) {\n rejectPending?.(new Error('Malformed Bluetooth notification'));\n return;\n }\n receive?.(hexToBytes(hex));\n });\n await this.bridge.subscribe(deviceId);\n\n const exchange = async <T>(\n frames: Uint8Array[],\n accept: (\n bytes: Uint8Array,\n resolve: (value: T) => void,\n reject: (error: unknown) => void\n ) => void,\n timeoutMs: number\n ): Promise<T> => {\n if (closed) throw new Error('Bluetooth connection ended');\n if (receive) throw new Error('Bluetooth exchange is busy');\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await new Promise<T>((resolve, reject) => {\n rejectPending = reject;\n receive = bytes => accept(bytes, resolve, reject);\n timer = setTimeout(\n () => reject(new SendApduTimeoutError('Bluetooth response timed out')),\n timeoutMs\n );\n void (async () => {\n for (const frame of frames) {\n if (closed) throw new Error('Bluetooth connection ended');\n await this.bridge.write(deviceId, toHex(frame));\n }\n })().catch(reject);\n });\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n receive = undefined;\n rejectPending = undefined;\n }\n };\n\n const frameSize = await exchange<number>(\n [Uint8Array.of(0x08, 0, 0, 0, 0)],\n (bytes, resolve, reject) => {\n if (bytes.length < 6 || bytes[0] !== 0x08 || bytes[5] < 6) {\n reject(new Error('Invalid Ledger BLE MTU response'));\n } else {\n // Matches @ledgerhq/hw-transport-web-ble inferMTU: the device\n // reports the frame size it can take, and it is only ever used to\n // raise the floor. Capping it here would negotiate and then throw\n // the answer away, leaving every transfer at ~7x the frame count.\n resolve(Math.max(LEDGER_BLE_MIN_FRAME_SIZE, bytes[5]));\n }\n },\n 30_000\n );\n if (closed) throw new Error('Bluetooth connection ended');\n const sender = this.args.apduSenderServiceFactory({ frameSize });\n const connected = new TransportConnectedDevice({\n ...known.device,\n type: 'BLE',\n sendApdu: async (apdu, _triggersDisconnection, abortTimeout): Promise<SendApduResult> => {\n if (closed) return Left(new DisconnectError());\n if (receive) return Left(new SendApduConcurrencyError());\n const receiver = this.args.apduReceiverServiceFactory();\n try {\n const result = await exchange<SendApduResult>(\n sender.getFrames(apdu).map(frame => frame.getRawData()),\n (bytes, resolve) =>\n receiver.handleFrame(bytes).caseOf({\n Left: error => resolve(Left(error)),\n Right: response => {\n response.map(value => resolve(Right(value)));\n },\n }),\n abortTimeout ?? 120_000\n );\n if (result.isLeft()) {\n await disconnect(true);\n }\n return result;\n } catch (error) {\n await disconnect(true);\n return Left(error instanceof SendApduTimeoutError ? error : new GeneralDmkError(error));\n }\n },\n });\n const connection = this.connections.get(deviceId);\n if (connection?.close === close) connection.connectedDevice = connected;\n return Right(connected);\n } catch (error) {\n await disconnect(false);\n return Left(new OpeningConnectionError(error));\n }\n }\n\n async disconnect({\n connectedDevice,\n }: Parameters<Transport['disconnect']>[0]): ReturnType<Transport['disconnect']> {\n const connection = this.connections.get(connectedDevice.id);\n if (connection?.connectedDevice !== connectedDevice || !connection.close())\n return Right(undefined);\n try {\n await this.bridge.disconnect(connectedDevice.id);\n return Right(undefined);\n } catch (error) {\n return Left(new DisconnectError(error));\n } finally {\n if (this.connections.get(connectedDevice.id) === connection) {\n this.connections.delete(connectedDevice.id);\n }\n }\n }\n}\n","import type { ElectronBleConnectOptions, ElectronBleMatch } from '@onekeyfe/hwk-adapter-core';\n\nexport const LEDGER_BLE_VENDOR = 'ledger';\n\n/**\n * Frame size bounds for the 0x08 handshake, kept together because they are two\n * halves of one rule: the device reports the size it can take in a single\n * byte, so 255 is the most it can ask for, and 20 is the floor we raise a\n * smaller answer to. The negotiated value in between is used as-is — capping\n * it would negotiate and then discard the answer, leaving every transfer at\n * roughly 7x the frame count.\n */\nexport const LEDGER_BLE_MIN_FRAME_SIZE = 20;\nexport const LEDGER_BLE_MAX_FRAME_SIZE = 255;\n\n/**\n * Ledger GATT uuids are per device model, so unlike Trezor's fixed profile\n * they are looked up from the DMK data source at discovery time and passed in\n * here. Only the vendor key and the framing rule are constant.\n */\nexport function ledgerBleMatch(serviceUuids: string[]): ElectronBleMatch {\n return { serviceUuids };\n}\n\nexport function ledgerBleConnectProfile(gatt: {\n serviceUuid: string;\n writeUuid: string;\n notifyUuid: string;\n}): ElectronBleConnectOptions {\n return {\n vendor: LEDGER_BLE_VENDOR,\n ...gatt,\n // Ledger frames carry their own length and sequence; padding corrupts them.\n write: { mode: 'raw', maxLength: LEDGER_BLE_MAX_FRAME_SIZE },\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -13,9 +13,26 @@ import {
|
|
|
13
13
|
} from "@ledgerhq/device-management-kit";
|
|
14
14
|
import { Left, Right } from "purify-ts";
|
|
15
15
|
import { defer, from, mergeMap } from "rxjs";
|
|
16
|
+
|
|
17
|
+
// src/bleProfile.ts
|
|
18
|
+
var LEDGER_BLE_VENDOR = "ledger";
|
|
19
|
+
var LEDGER_BLE_MIN_FRAME_SIZE = 20;
|
|
20
|
+
var LEDGER_BLE_MAX_FRAME_SIZE = 255;
|
|
21
|
+
function ledgerBleMatch(serviceUuids) {
|
|
22
|
+
return { serviceUuids };
|
|
23
|
+
}
|
|
24
|
+
function ledgerBleConnectProfile(gatt) {
|
|
25
|
+
return {
|
|
26
|
+
vendor: LEDGER_BLE_VENDOR,
|
|
27
|
+
...gatt,
|
|
28
|
+
// Ledger frames carry their own length and sequence; padding corrupts them.
|
|
29
|
+
write: { mode: "raw", maxLength: LEDGER_BLE_MAX_FRAME_SIZE }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/LedgerElectronBleTransport.ts
|
|
16
34
|
import { hexToBytes } from "@onekeyfe/hwk-adapter-core";
|
|
17
35
|
var TRANSPORT_ID = "ELECTRON_BLE";
|
|
18
|
-
var FRAME_SIZE = 20;
|
|
19
36
|
var normalizeUuid = (uuid) => uuid.replace(/-/g, "").toLowerCase();
|
|
20
37
|
var toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
21
38
|
var LedgerElectronBleTransport = class {
|
|
@@ -55,8 +72,8 @@ var LedgerElectronBleTransport = class {
|
|
|
55
72
|
const availability = await this.bridge.checkAvailability();
|
|
56
73
|
if (!availability.available) throw new Error(`Bluetooth unavailable: ${availability.state}`);
|
|
57
74
|
const infos = await this.bridge.scan({
|
|
58
|
-
vendor:
|
|
59
|
-
|
|
75
|
+
vendor: LEDGER_BLE_VENDOR,
|
|
76
|
+
match: ledgerBleMatch(this.args.deviceModelDataSource.getBluetoothServices())
|
|
60
77
|
});
|
|
61
78
|
const devices = [];
|
|
62
79
|
for (const info of infos) {
|
|
@@ -122,12 +139,14 @@ var LedgerElectronBleTransport = class {
|
|
|
122
139
|
release();
|
|
123
140
|
return Left(new UnknownDeviceError());
|
|
124
141
|
}
|
|
125
|
-
await this.bridge.connect(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
await this.bridge.connect(
|
|
143
|
+
deviceId,
|
|
144
|
+
ledgerBleConnectProfile({
|
|
145
|
+
serviceUuid: known.profile.serviceUuid,
|
|
146
|
+
writeUuid: known.profile.writeUuid,
|
|
147
|
+
notifyUuid: known.profile.notifyUuid
|
|
148
|
+
})
|
|
149
|
+
);
|
|
131
150
|
removeDisconnect = this.bridge.onDeviceDisconnected((id) => {
|
|
132
151
|
if (id !== deviceId) return;
|
|
133
152
|
if (close()) {
|
|
@@ -174,7 +193,9 @@ var LedgerElectronBleTransport = class {
|
|
|
174
193
|
(bytes, resolve, reject) => {
|
|
175
194
|
if (bytes.length < 6 || bytes[0] !== 8 || bytes[5] < 6) {
|
|
176
195
|
reject(new Error("Invalid Ledger BLE MTU response"));
|
|
177
|
-
} else
|
|
196
|
+
} else {
|
|
197
|
+
resolve(Math.max(LEDGER_BLE_MIN_FRAME_SIZE, bytes[5]));
|
|
198
|
+
}
|
|
178
199
|
},
|
|
179
200
|
3e4
|
|
180
201
|
);
|
|
@@ -243,7 +264,12 @@ function createLedgerElectronBleConnector(bridge) {
|
|
|
243
264
|
);
|
|
244
265
|
}
|
|
245
266
|
export {
|
|
267
|
+
LEDGER_BLE_MAX_FRAME_SIZE,
|
|
268
|
+
LEDGER_BLE_MIN_FRAME_SIZE,
|
|
269
|
+
LEDGER_BLE_VENDOR,
|
|
246
270
|
LedgerElectronBleTransport,
|
|
247
|
-
createLedgerElectronBleConnector
|
|
271
|
+
createLedgerElectronBleConnector,
|
|
272
|
+
ledgerBleConnectProfile,
|
|
273
|
+
ledgerBleMatch
|
|
248
274
|
};
|
|
249
275
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/LedgerElectronBleConnector.ts","../src/LedgerElectronBleTransport.ts"],"sourcesContent":["import { LedgerConnectorBase } from '@onekeyfe/hwk-ledger-adapter';\nimport { LedgerElectronBleTransport } from './LedgerElectronBleTransport';\n\nimport type { TransportArgs } from '@ledgerhq/device-management-kit';\nimport type { ElectronBleApi } from '@onekeyfe/hwk-adapter-core';\n\nexport function createLedgerElectronBleConnector(bridge: ElectronBleApi): LedgerConnectorBase {\n return new LedgerConnectorBase(\n async () => (args: TransportArgs) => new LedgerElectronBleTransport(bridge, args),\n { connectionType: 'ble' }\n );\n}\n","import {\n DisconnectError,\n GeneralDmkError,\n OpeningConnectionError,\n SendApduConcurrencyError,\n SendApduTimeoutError,\n TransportConnectedDevice,\n UnknownDeviceError,\n} from '@ledgerhq/device-management-kit';\nimport { Left, Right } from 'purify-ts';\nimport { defer, from, mergeMap } from 'rxjs';\nimport { hexToBytes } from '@onekeyfe/hwk-adapter-core';\n\nimport type {\n BleDeviceInfos,\n SendApduResult,\n Transport,\n TransportArgs,\n TransportDiscoveredDevice,\n} from '@ledgerhq/device-management-kit';\nimport type { ElectronBleApi, ElectronBleDeviceInfo } from '@onekeyfe/hwk-adapter-core';\n\nconst TRANSPORT_ID = 'ELECTRON_BLE';\n// The ATT default is safe on both native backends; never split a Ledger frame in main.\nconst FRAME_SIZE = 20;\nconst normalizeUuid = (uuid: string) => uuid.replace(/-/g, '').toLowerCase();\nconst toHex = (bytes: Uint8Array) =>\n Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');\n\nexport class LedgerElectronBleTransport implements Transport {\n private readonly devices = new Map<\n string,\n { device: TransportDiscoveredDevice; profile: BleDeviceInfos }\n >();\n\n private readonly connections = new Map<\n string,\n {\n close: () => boolean;\n connectedDevice?: TransportConnectedDevice;\n }\n >();\n\n private scanPromise: Promise<TransportDiscoveredDevice[]> | undefined;\n\n private readonly bridge: ElectronBleApi;\n\n private readonly args: Pick<\n TransportArgs,\n 'deviceModelDataSource' | 'apduSenderServiceFactory' | 'apduReceiverServiceFactory'\n >;\n\n constructor(bridge: ElectronBleApi, args: LedgerElectronBleTransport['args']) {\n this.bridge = bridge;\n this.args = args;\n }\n\n getIdentifier() {\n return TRANSPORT_ID;\n }\n\n isSupported() {\n return true;\n }\n\n private remember(info: ElectronBleDeviceInfo): TransportDiscoveredDevice | undefined {\n const profiles = this.args.deviceModelDataSource.getBluetoothServicesInfos();\n const profile = Object.values(profiles).find(candidate =>\n info.advertisedServiceUuids?.some(\n uuid => normalizeUuid(uuid) === normalizeUuid(candidate.serviceUuid)\n )\n );\n if (!profile) return undefined;\n const device: TransportDiscoveredDevice = {\n id: info.id,\n name: info.name,\n rssi: info.rssi,\n deviceModel: profile.deviceModel,\n transport: TRANSPORT_ID,\n };\n this.devices.set(info.id, { device, profile });\n return device;\n }\n\n private discover(): Promise<TransportDiscoveredDevice[]> {\n if (this.scanPromise) return this.scanPromise;\n const scan = (async () => {\n const availability = await this.bridge.checkAvailability();\n if (!availability.available) throw new Error(`Bluetooth unavailable: ${availability.state}`);\n const infos = await this.bridge.scan({\n vendor: 'ledger',\n serviceUuids: this.args.deviceModelDataSource.getBluetoothServices(),\n });\n const devices: TransportDiscoveredDevice[] = [];\n for (const info of infos) {\n const device = this.remember(info);\n if (device) devices.push(device);\n }\n return devices;\n })();\n this.scanPromise = scan;\n void scan\n .finally(() => {\n if (this.scanPromise === scan) this.scanPromise = undefined;\n })\n .catch(() => undefined);\n return scan;\n }\n\n startDiscovering() {\n return defer(() => this.discover()).pipe(mergeMap(devices => from(devices)));\n }\n\n listenToAvailableDevices() {\n return defer(() => this.discover());\n }\n\n async stopDiscovering(): Promise<void> {\n await this.scanPromise?.catch(() => undefined);\n await this.bridge.stopScan();\n }\n\n async connect({\n deviceId,\n onDisconnect,\n }: Parameters<Transport['connect']>[0]): ReturnType<Transport['connect']> {\n if (this.connections.has(deviceId))\n return Left(new OpeningConnectionError('Device is already connected'));\n let removeNotification: (() => void) | undefined;\n let removeDisconnect: (() => void) | undefined;\n let closed = false;\n let rejectPending: ((error: unknown) => void) | undefined;\n let receive: ((bytes: Uint8Array) => void) | undefined;\n const close = () => {\n if (closed) return false;\n closed = true;\n removeNotification?.();\n removeDisconnect?.();\n rejectPending?.(new Error('Bluetooth connection ended'));\n return true;\n };\n const release = () => {\n if (this.connections.get(deviceId)?.close === close) this.connections.delete(deviceId);\n };\n const disconnect = async (notify: boolean) => {\n if (!close()) return;\n await this.bridge.disconnect(deviceId).catch(() => undefined);\n release();\n if (notify) onDisconnect(deviceId);\n };\n // Reserve before the first await so concurrent acquires cannot share native GATT state.\n this.connections.set(deviceId, { close });\n try {\n await this.stopDiscovering();\n if (!this.devices.has(deviceId)) {\n const info = await this.bridge.getDevice(deviceId);\n if (info) this.remember(info);\n }\n const known = this.devices.get(deviceId);\n if (!known) {\n close();\n release();\n return Left(new UnknownDeviceError());\n }\n await this.bridge.connect(deviceId, {\n vendor: 'ledger',\n serviceUuid: known.profile.serviceUuid,\n writeUuid: known.profile.writeUuid,\n notifyUuid: known.profile.notifyUuid,\n });\n removeDisconnect = this.bridge.onDeviceDisconnected(id => {\n if (id !== deviceId) return;\n if (close()) {\n release();\n onDisconnect(deviceId);\n }\n });\n removeNotification = this.bridge.onNotification((id, hex) => {\n if (id !== deviceId || closed) return;\n if (!/^(?:[0-9a-f]{2})+$/i.test(hex)) {\n rejectPending?.(new Error('Malformed Bluetooth notification'));\n return;\n }\n receive?.(hexToBytes(hex));\n });\n await this.bridge.subscribe(deviceId);\n\n const exchange = async <T>(\n frames: Uint8Array[],\n accept: (\n bytes: Uint8Array,\n resolve: (value: T) => void,\n reject: (error: unknown) => void\n ) => void,\n timeoutMs: number\n ): Promise<T> => {\n if (closed) throw new Error('Bluetooth connection ended');\n if (receive) throw new Error('Bluetooth exchange is busy');\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await new Promise<T>((resolve, reject) => {\n rejectPending = reject;\n receive = bytes => accept(bytes, resolve, reject);\n timer = setTimeout(\n () => reject(new SendApduTimeoutError('Bluetooth response timed out')),\n timeoutMs\n );\n void (async () => {\n for (const frame of frames) {\n if (closed) throw new Error('Bluetooth connection ended');\n await this.bridge.write(deviceId, toHex(frame));\n }\n })().catch(reject);\n });\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n receive = undefined;\n rejectPending = undefined;\n }\n };\n\n const frameSize = await exchange<number>(\n [Uint8Array.of(0x08, 0, 0, 0, 0)],\n (bytes, resolve, reject) => {\n if (bytes.length < 6 || bytes[0] !== 0x08 || bytes[5] < 6) {\n reject(new Error('Invalid Ledger BLE MTU response'));\n } else resolve(Math.min(FRAME_SIZE, bytes[5]));\n },\n 30_000\n );\n if (closed) throw new Error('Bluetooth connection ended');\n const sender = this.args.apduSenderServiceFactory({ frameSize });\n const connected = new TransportConnectedDevice({\n ...known.device,\n type: 'BLE',\n sendApdu: async (apdu, _triggersDisconnection, abortTimeout): Promise<SendApduResult> => {\n if (closed) return Left(new DisconnectError());\n if (receive) return Left(new SendApduConcurrencyError());\n const receiver = this.args.apduReceiverServiceFactory();\n try {\n const result = await exchange<SendApduResult>(\n sender.getFrames(apdu).map(frame => frame.getRawData()),\n (bytes, resolve) =>\n receiver.handleFrame(bytes).caseOf({\n Left: error => resolve(Left(error)),\n Right: response => {\n response.map(value => resolve(Right(value)));\n },\n }),\n abortTimeout ?? 120_000\n );\n if (result.isLeft()) {\n await disconnect(true);\n }\n return result;\n } catch (error) {\n await disconnect(true);\n return Left(error instanceof SendApduTimeoutError ? error : new GeneralDmkError(error));\n }\n },\n });\n const connection = this.connections.get(deviceId);\n if (connection?.close === close) connection.connectedDevice = connected;\n return Right(connected);\n } catch (error) {\n await disconnect(false);\n return Left(new OpeningConnectionError(error));\n }\n }\n\n async disconnect({\n connectedDevice,\n }: Parameters<Transport['disconnect']>[0]): ReturnType<Transport['disconnect']> {\n const connection = this.connections.get(connectedDevice.id);\n if (connection?.connectedDevice !== connectedDevice || !connection.close())\n return Right(undefined);\n try {\n await this.bridge.disconnect(connectedDevice.id);\n return Right(undefined);\n } catch (error) {\n return Left(new DisconnectError(error));\n } finally {\n if (this.connections.get(connectedDevice.id) === connection) {\n this.connections.delete(connectedDevice.id);\n }\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/LedgerElectronBleConnector.ts","../src/LedgerElectronBleTransport.ts","../src/bleProfile.ts"],"sourcesContent":["import { LedgerConnectorBase } from '@onekeyfe/hwk-ledger-adapter';\nimport { LedgerElectronBleTransport } from './LedgerElectronBleTransport';\n\nimport type { TransportArgs } from '@ledgerhq/device-management-kit';\nimport type { ElectronBleApi } from '@onekeyfe/hwk-adapter-core';\n\nexport function createLedgerElectronBleConnector(bridge: ElectronBleApi): LedgerConnectorBase {\n return new LedgerConnectorBase(\n async () => (args: TransportArgs) => new LedgerElectronBleTransport(bridge, args),\n { connectionType: 'ble' }\n );\n}\n","import {\n DisconnectError,\n GeneralDmkError,\n OpeningConnectionError,\n SendApduConcurrencyError,\n SendApduTimeoutError,\n TransportConnectedDevice,\n UnknownDeviceError,\n} from '@ledgerhq/device-management-kit';\nimport { Left, Right } from 'purify-ts';\nimport { defer, from, mergeMap } from 'rxjs';\nimport {\n LEDGER_BLE_MIN_FRAME_SIZE,\n LEDGER_BLE_VENDOR,\n ledgerBleConnectProfile,\n ledgerBleMatch,\n} from './bleProfile';\nimport { hexToBytes } from '@onekeyfe/hwk-adapter-core';\n\nimport type {\n BleDeviceInfos,\n SendApduResult,\n Transport,\n TransportArgs,\n TransportDiscoveredDevice,\n} from '@ledgerhq/device-management-kit';\nimport type { ElectronBleApi, ElectronBleDeviceInfo } from '@onekeyfe/hwk-adapter-core';\n\nconst TRANSPORT_ID = 'ELECTRON_BLE';\n// Ledger's own transports open at the ATT default and then raise it from the\n// device's answer to the 0x08 handshake below. Keep the same floor so a device\n// that reports nothing useful still works.\nconst normalizeUuid = (uuid: string) => uuid.replace(/-/g, '').toLowerCase();\nconst toHex = (bytes: Uint8Array) =>\n Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');\n\nexport class LedgerElectronBleTransport implements Transport {\n private readonly devices = new Map<\n string,\n { device: TransportDiscoveredDevice; profile: BleDeviceInfos }\n >();\n\n private readonly connections = new Map<\n string,\n {\n close: () => boolean;\n connectedDevice?: TransportConnectedDevice;\n }\n >();\n\n private scanPromise: Promise<TransportDiscoveredDevice[]> | undefined;\n\n private readonly bridge: ElectronBleApi;\n\n private readonly args: Pick<\n TransportArgs,\n 'deviceModelDataSource' | 'apduSenderServiceFactory' | 'apduReceiverServiceFactory'\n >;\n\n constructor(bridge: ElectronBleApi, args: LedgerElectronBleTransport['args']) {\n this.bridge = bridge;\n this.args = args;\n }\n\n getIdentifier() {\n return TRANSPORT_ID;\n }\n\n isSupported() {\n return true;\n }\n\n private remember(info: ElectronBleDeviceInfo): TransportDiscoveredDevice | undefined {\n const profiles = this.args.deviceModelDataSource.getBluetoothServicesInfos();\n const profile = Object.values(profiles).find(candidate =>\n info.advertisedServiceUuids?.some(\n uuid => normalizeUuid(uuid) === normalizeUuid(candidate.serviceUuid)\n )\n );\n if (!profile) return undefined;\n const device: TransportDiscoveredDevice = {\n id: info.id,\n name: info.name,\n rssi: info.rssi,\n deviceModel: profile.deviceModel,\n transport: TRANSPORT_ID,\n };\n this.devices.set(info.id, { device, profile });\n return device;\n }\n\n private discover(): Promise<TransportDiscoveredDevice[]> {\n if (this.scanPromise) return this.scanPromise;\n const scan = (async () => {\n const availability = await this.bridge.checkAvailability();\n if (!availability.available) throw new Error(`Bluetooth unavailable: ${availability.state}`);\n const infos = await this.bridge.scan({\n vendor: LEDGER_BLE_VENDOR,\n match: ledgerBleMatch(this.args.deviceModelDataSource.getBluetoothServices()),\n });\n const devices: TransportDiscoveredDevice[] = [];\n for (const info of infos) {\n const device = this.remember(info);\n if (device) devices.push(device);\n }\n return devices;\n })();\n this.scanPromise = scan;\n void scan\n .finally(() => {\n if (this.scanPromise === scan) this.scanPromise = undefined;\n })\n .catch(() => undefined);\n return scan;\n }\n\n startDiscovering() {\n return defer(() => this.discover()).pipe(mergeMap(devices => from(devices)));\n }\n\n listenToAvailableDevices() {\n return defer(() => this.discover());\n }\n\n async stopDiscovering(): Promise<void> {\n await this.scanPromise?.catch(() => undefined);\n await this.bridge.stopScan();\n }\n\n async connect({\n deviceId,\n onDisconnect,\n }: Parameters<Transport['connect']>[0]): ReturnType<Transport['connect']> {\n if (this.connections.has(deviceId))\n return Left(new OpeningConnectionError('Device is already connected'));\n let removeNotification: (() => void) | undefined;\n let removeDisconnect: (() => void) | undefined;\n let closed = false;\n let rejectPending: ((error: unknown) => void) | undefined;\n let receive: ((bytes: Uint8Array) => void) | undefined;\n const close = () => {\n if (closed) return false;\n closed = true;\n removeNotification?.();\n removeDisconnect?.();\n rejectPending?.(new Error('Bluetooth connection ended'));\n return true;\n };\n const release = () => {\n if (this.connections.get(deviceId)?.close === close) this.connections.delete(deviceId);\n };\n const disconnect = async (notify: boolean) => {\n if (!close()) return;\n await this.bridge.disconnect(deviceId).catch(() => undefined);\n release();\n if (notify) onDisconnect(deviceId);\n };\n // Reserve before the first await so concurrent acquires cannot share native GATT state.\n this.connections.set(deviceId, { close });\n try {\n await this.stopDiscovering();\n if (!this.devices.has(deviceId)) {\n const info = await this.bridge.getDevice(deviceId);\n if (info) this.remember(info);\n }\n const known = this.devices.get(deviceId);\n if (!known) {\n close();\n release();\n return Left(new UnknownDeviceError());\n }\n await this.bridge.connect(\n deviceId,\n ledgerBleConnectProfile({\n serviceUuid: known.profile.serviceUuid,\n writeUuid: known.profile.writeUuid,\n notifyUuid: known.profile.notifyUuid,\n })\n );\n removeDisconnect = this.bridge.onDeviceDisconnected(id => {\n if (id !== deviceId) return;\n if (close()) {\n release();\n onDisconnect(deviceId);\n }\n });\n removeNotification = this.bridge.onNotification((id, hex) => {\n if (id !== deviceId || closed) return;\n if (!/^(?:[0-9a-f]{2})+$/i.test(hex)) {\n rejectPending?.(new Error('Malformed Bluetooth notification'));\n return;\n }\n receive?.(hexToBytes(hex));\n });\n await this.bridge.subscribe(deviceId);\n\n const exchange = async <T>(\n frames: Uint8Array[],\n accept: (\n bytes: Uint8Array,\n resolve: (value: T) => void,\n reject: (error: unknown) => void\n ) => void,\n timeoutMs: number\n ): Promise<T> => {\n if (closed) throw new Error('Bluetooth connection ended');\n if (receive) throw new Error('Bluetooth exchange is busy');\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await new Promise<T>((resolve, reject) => {\n rejectPending = reject;\n receive = bytes => accept(bytes, resolve, reject);\n timer = setTimeout(\n () => reject(new SendApduTimeoutError('Bluetooth response timed out')),\n timeoutMs\n );\n void (async () => {\n for (const frame of frames) {\n if (closed) throw new Error('Bluetooth connection ended');\n await this.bridge.write(deviceId, toHex(frame));\n }\n })().catch(reject);\n });\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n receive = undefined;\n rejectPending = undefined;\n }\n };\n\n const frameSize = await exchange<number>(\n [Uint8Array.of(0x08, 0, 0, 0, 0)],\n (bytes, resolve, reject) => {\n if (bytes.length < 6 || bytes[0] !== 0x08 || bytes[5] < 6) {\n reject(new Error('Invalid Ledger BLE MTU response'));\n } else {\n // Matches @ledgerhq/hw-transport-web-ble inferMTU: the device\n // reports the frame size it can take, and it is only ever used to\n // raise the floor. Capping it here would negotiate and then throw\n // the answer away, leaving every transfer at ~7x the frame count.\n resolve(Math.max(LEDGER_BLE_MIN_FRAME_SIZE, bytes[5]));\n }\n },\n 30_000\n );\n if (closed) throw new Error('Bluetooth connection ended');\n const sender = this.args.apduSenderServiceFactory({ frameSize });\n const connected = new TransportConnectedDevice({\n ...known.device,\n type: 'BLE',\n sendApdu: async (apdu, _triggersDisconnection, abortTimeout): Promise<SendApduResult> => {\n if (closed) return Left(new DisconnectError());\n if (receive) return Left(new SendApduConcurrencyError());\n const receiver = this.args.apduReceiverServiceFactory();\n try {\n const result = await exchange<SendApduResult>(\n sender.getFrames(apdu).map(frame => frame.getRawData()),\n (bytes, resolve) =>\n receiver.handleFrame(bytes).caseOf({\n Left: error => resolve(Left(error)),\n Right: response => {\n response.map(value => resolve(Right(value)));\n },\n }),\n abortTimeout ?? 120_000\n );\n if (result.isLeft()) {\n await disconnect(true);\n }\n return result;\n } catch (error) {\n await disconnect(true);\n return Left(error instanceof SendApduTimeoutError ? error : new GeneralDmkError(error));\n }\n },\n });\n const connection = this.connections.get(deviceId);\n if (connection?.close === close) connection.connectedDevice = connected;\n return Right(connected);\n } catch (error) {\n await disconnect(false);\n return Left(new OpeningConnectionError(error));\n }\n }\n\n async disconnect({\n connectedDevice,\n }: Parameters<Transport['disconnect']>[0]): ReturnType<Transport['disconnect']> {\n const connection = this.connections.get(connectedDevice.id);\n if (connection?.connectedDevice !== connectedDevice || !connection.close())\n return Right(undefined);\n try {\n await this.bridge.disconnect(connectedDevice.id);\n return Right(undefined);\n } catch (error) {\n return Left(new DisconnectError(error));\n } finally {\n if (this.connections.get(connectedDevice.id) === connection) {\n this.connections.delete(connectedDevice.id);\n }\n }\n }\n}\n","import type { ElectronBleConnectOptions, ElectronBleMatch } from '@onekeyfe/hwk-adapter-core';\n\nexport const LEDGER_BLE_VENDOR = 'ledger';\n\n/**\n * Frame size bounds for the 0x08 handshake, kept together because they are two\n * halves of one rule: the device reports the size it can take in a single\n * byte, so 255 is the most it can ask for, and 20 is the floor we raise a\n * smaller answer to. The negotiated value in between is used as-is — capping\n * it would negotiate and then discard the answer, leaving every transfer at\n * roughly 7x the frame count.\n */\nexport const LEDGER_BLE_MIN_FRAME_SIZE = 20;\nexport const LEDGER_BLE_MAX_FRAME_SIZE = 255;\n\n/**\n * Ledger GATT uuids are per device model, so unlike Trezor's fixed profile\n * they are looked up from the DMK data source at discovery time and passed in\n * here. Only the vendor key and the framing rule are constant.\n */\nexport function ledgerBleMatch(serviceUuids: string[]): ElectronBleMatch {\n return { serviceUuids };\n}\n\nexport function ledgerBleConnectProfile(gatt: {\n serviceUuid: string;\n writeUuid: string;\n notifyUuid: string;\n}): ElectronBleConnectOptions {\n return {\n vendor: LEDGER_BLE_VENDOR,\n ...gatt,\n // Ledger frames carry their own length and sequence; padding corrupts them.\n write: { mode: 'raw', maxLength: LEDGER_BLE_MAX_FRAME_SIZE },\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hwk-ledger-connector-electron-ble",
|
|
3
|
-
"version": "1.2.3-alpha.
|
|
3
|
+
"version": "1.2.3-alpha.2",
|
|
4
4
|
"description": "IConnector implementation for Ledger hardware wallets via Electron BLE",
|
|
5
5
|
"author": "OneKey",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@ledgerhq/device-management-kit": "1.9.0",
|
|
52
|
-
"@onekeyfe/hwk-adapter-core": "1.2.3-alpha.
|
|
53
|
-
"@onekeyfe/hwk-ledger-adapter": "1.2.3-alpha.
|
|
52
|
+
"@onekeyfe/hwk-adapter-core": "1.2.3-alpha.2",
|
|
53
|
+
"@onekeyfe/hwk-ledger-adapter": "1.2.3-alpha.2",
|
|
54
54
|
"purify-ts": "2.1.0",
|
|
55
55
|
"rxjs": "7.8.2"
|
|
56
56
|
},
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"tsup": "^8.0.0",
|
|
60
60
|
"typescript": "5.1.6"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "8b790c78394debdd3d9999db2f6a56235efb7a18"
|
|
63
63
|
}
|