@onekeyfe/hardware-cli 1.2.0-alpha.2 → 1.2.0-alpha.21

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.
@@ -0,0 +1,19 @@
1
+ import type { CoreApi, DeviceStateScope } from '@onekeyfe/hd-core';
2
+ type DeviceStateSdk = Pick<CoreApi, 'searchDevices' | 'getDeviceState' | 'getFeatures'>;
3
+ /**
4
+ * Unified state entry for the new CLI. Resolve the user-facing connectId through
5
+ * discovery so V1 serial IDs map to the process-local USB path.
6
+ */
7
+ export declare const getCanonicalDeviceState: (sdk: DeviceStateSdk, connectId: string | undefined, scope: DeviceStateScope) => Promise<import("@onekeyfe/hd-core").Unsuccessful | {
8
+ success: true;
9
+ payload: import("@onekeyfe/hd-core").DeviceState;
10
+ }>;
11
+ /**
12
+ * Legacy CLI only: retain getFeatures for V1 and reuse the discovery projection for V2.
13
+ * Public SDK.getFeatures remains V1-only.
14
+ */
15
+ export declare const getCompatibleFeatures: (sdk: DeviceStateSdk, connectId?: string) => Promise<import("@onekeyfe/hd-core").Unsuccessful | {
16
+ success: true;
17
+ payload: import("@onekeyfe/hd-core").Features;
18
+ }>;
19
+ export {};
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCompatibleFeatures = exports.getCanonicalDeviceState = void 0;
4
+ const createDeviceNotFoundResult = (connectId) => ({
5
+ success: false,
6
+ payload: {
7
+ code: 'DEVICE_NOT_FOUND',
8
+ error: connectId ? `Device not found: ${connectId}` : 'No device found',
9
+ },
10
+ });
11
+ const resolveSearchDevice = async (sdk, connectId) => {
12
+ const searchResult = await sdk.searchDevices();
13
+ if (!searchResult.success)
14
+ return searchResult;
15
+ const devices = searchResult.payload;
16
+ const device = connectId ? devices.find(item => item.connectId === connectId) : devices[0];
17
+ if (!device?.connectId)
18
+ return createDeviceNotFoundResult(connectId);
19
+ return { success: true, payload: device };
20
+ };
21
+ /**
22
+ * Unified state entry for the new CLI. Resolve the user-facing connectId through
23
+ * discovery so V1 serial IDs map to the process-local USB path.
24
+ */
25
+ const getCanonicalDeviceState = async (sdk, connectId, scope) => {
26
+ const deviceResult = await resolveSearchDevice(sdk, connectId);
27
+ if (!deviceResult.success)
28
+ return deviceResult;
29
+ if (scope === 'runtime' && deviceResult.payload.state) {
30
+ return { success: true, payload: deviceResult.payload.state };
31
+ }
32
+ const resolvedConnectId = deviceResult.payload.connectId ?? undefined;
33
+ if (!resolvedConnectId)
34
+ return createDeviceNotFoundResult();
35
+ return sdk.getDeviceState(resolvedConnectId, { scope });
36
+ };
37
+ exports.getCanonicalDeviceState = getCanonicalDeviceState;
38
+ /**
39
+ * Legacy CLI only: retain getFeatures for V1 and reuse the discovery projection for V2.
40
+ * Public SDK.getFeatures remains V1-only.
41
+ */
42
+ const getCompatibleFeatures = async (sdk, connectId) => {
43
+ const deviceResult = await resolveSearchDevice(sdk, connectId);
44
+ if (!deviceResult.success)
45
+ return deviceResult;
46
+ const device = deviceResult.payload;
47
+ const protocol = device.state?.protocol ?? device.features?.protocol;
48
+ if (protocol === 'V2') {
49
+ if (!device.features) {
50
+ return {
51
+ success: false,
52
+ payload: {
53
+ code: 'DEVICE_FEATURES_UNAVAILABLE',
54
+ error: 'Protocol V2 compatibility features are unavailable',
55
+ },
56
+ };
57
+ }
58
+ return { success: true, payload: device.features };
59
+ }
60
+ return sdk.getFeatures(device.connectId ?? '');
61
+ };
62
+ exports.getCompatibleFeatures = getCompatibleFeatures;
package/dist/sdk.d.ts CHANGED
@@ -13,6 +13,8 @@ export interface SDKOptions {
13
13
  connectId?: string;
14
14
  passphraseState?: string;
15
15
  useEmptyPassphrase?: boolean;
16
+ debug?: boolean;
17
+ transport?: 'usb' | 'ble';
16
18
  }
17
19
  export declare function createSDK(opts: SDKOptions): Promise<typeof HardwareSDK>;
18
20
  /**
package/dist/sdk.js CHANGED
@@ -41,6 +41,7 @@ const readline = __importStar(require("node:readline"));
41
41
  const hd_common_connect_sdk_1 = __importDefault(require("@onekeyfe/hd-common-connect-sdk"));
42
42
  const hd_core_1 = require("@onekeyfe/hd-core");
43
43
  const pinentry_1 = require("./pinentry");
44
+ const nobleBlePlugin_1 = require("./transports/nobleBlePlugin");
44
45
  /**
45
46
  * Current per-invocation CLI options. Event handlers read from this object
46
47
  * so that invoking createSDK() with different opts never results in stale
@@ -167,12 +168,12 @@ function registerEventHandlers(sdk) {
167
168
  process.stderr.write('[onekey-hw] Please confirm the action on your device...\n');
168
169
  }
169
170
  });
170
- sdk.on(hd_core_1.DEVICE.CONNECT, (device) => {
171
+ sdk.on(hd_core_1.DEVICE.CONNECT, ({ device }) => {
171
172
  const name = device?.label || device?.name;
172
173
  if (name)
173
174
  process.stderr.write(`[onekey-hw] Device connected: ${name}\n`);
174
175
  });
175
- sdk.on(hd_core_1.DEVICE.DISCONNECT, (device) => {
176
+ sdk.on(hd_core_1.DEVICE.DISCONNECT, ({ device }) => {
176
177
  const name = device?.label || device?.name;
177
178
  if (name)
178
179
  process.stderr.write(`[onekey-hw] Device disconnected: ${name}\n`);
@@ -182,12 +183,13 @@ function registerEventHandlers(sdk) {
182
183
  // SDK Factory
183
184
  // ---------------------------------------------------------------------------
184
185
  async function initSDK() {
186
+ const transport = currentOpts.transport ?? 'usb';
185
187
  const settings = {
186
- debug: false,
188
+ debug: currentOpts.debug ?? false,
187
189
  fetchConfig: true,
188
- env: 'node-usb',
190
+ env: transport === 'ble' ? 'lowlevel' : 'node-usb',
189
191
  };
190
- await hd_common_connect_sdk_1.default.init(settings);
192
+ await hd_common_connect_sdk_1.default.init(settings, undefined, transport === 'ble' ? (0, nobleBlePlugin_1.createNobleBlePlugin)() : undefined);
191
193
  // Defensive: strip any stale listeners (e.g. left over from a previous
192
194
  // dispose/init cycle in a long-running process) before wiring ours.
193
195
  // Mirrors app-monorepo's cleanupHardwareSDKInstance() which removes
@@ -220,7 +222,7 @@ async function disposeSDK() {
220
222
  return;
221
223
  try {
222
224
  const sdk = await sdkReadyPromise;
223
- sdk.dispose();
225
+ await Promise.resolve(sdk.dispose());
224
226
  }
225
227
  catch {
226
228
  // ignore errors during cleanup
@@ -0,0 +1,2 @@
1
+ import type { LowlevelTransportSharedPlugin } from '@onekeyfe/hd-transport';
2
+ export declare function createNobleBlePlugin(): LowlevelTransportSharedPlugin;
@@ -0,0 +1,384 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createNobleBlePlugin = void 0;
4
+ const hd_shared_1 = require("@onekeyfe/hd-shared");
5
+ const ONEKEY_SERVICE_UUIDS = [hd_shared_1.ONEKEY_SERVICE_UUID];
6
+ const PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS = new Set(['fffd']);
7
+ const NORMALIZED_WRITE_UUID = '0002';
8
+ const NORMALIZED_NOTIFY_UUID = '0003';
9
+ const NORMALIZED_ONEKEY_SERVICE_UUIDS = new Set([
10
+ ...ONEKEY_SERVICE_UUIDS.map(uuid => getBleUuidKey(uuid)),
11
+ '0001',
12
+ ]);
13
+ const BLUETOOTH_INIT_TIMEOUT = 10000;
14
+ const DEVICE_SCAN_TIMEOUT = 8000;
15
+ const CONNECTION_TIMEOUT = 8000;
16
+ const SERVICE_DISCOVERY_TIMEOUT = 10000;
17
+ const BLE_CLEANUP_TIMEOUT = 100;
18
+ const BLE_PACKET_SIZE = 192;
19
+ const BLE_ENCRYPTION_ERROR_PATTERNS = [/encryption is insufficient/i, /insufficient encryption/i];
20
+ let noble = null;
21
+ let nobleReadyPromise = null;
22
+ const discoveredDevices = new Map();
23
+ const connectedDevices = new Map();
24
+ const deviceCharacteristics = new Map();
25
+ const notificationStates = new Map();
26
+ const notificationGenerations = new Map();
27
+ function getBleUuidKey(uuid) {
28
+ const normalized = (uuid ?? '').replace(/-/g, '').toLowerCase();
29
+ return normalized.length >= 8 ? normalized.substring(4, 8) : normalized;
30
+ }
31
+ function isGenericBleService(uuid) {
32
+ return ['1800', '1801', '180a', '180f'].includes(getBleUuidKey(uuid));
33
+ }
34
+ function hasOneKeyAdvertisementService(peripheral) {
35
+ const serviceUuids = peripheral.advertisement?.serviceUuids ?? [];
36
+ return serviceUuids.some(uuid => {
37
+ const uuidKey = getBleUuidKey(uuid);
38
+ return (NORMALIZED_ONEKEY_SERVICE_UUIDS.has(uuidKey) ||
39
+ PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS.has(uuidKey));
40
+ });
41
+ }
42
+ function isOneKeyPeripheral(peripheral) {
43
+ const deviceName = peripheral.advertisement?.localName || null;
44
+ return (0, hd_shared_1.isOnekeyDevice)(deviceName, peripheral.id) || hasOneKeyAdvertisementService(peripheral);
45
+ }
46
+ function enqueueNotification(deviceId, generation, data) {
47
+ const state = notificationStates.get(deviceId);
48
+ if (!state || state.generation !== generation)
49
+ return;
50
+ const hex = data.toString('hex');
51
+ const [receiver] = state.pendingReceivers;
52
+ if (receiver) {
53
+ state.pendingReceivers.delete(receiver);
54
+ receiver.resolve(hex);
55
+ return;
56
+ }
57
+ state.queue.push(hex);
58
+ }
59
+ function createNotificationState(deviceId) {
60
+ const existing = notificationStates.get(deviceId);
61
+ if (existing) {
62
+ const error = new Error(`BLE notification state replaced for ${deviceId}`);
63
+ existing.pendingReceivers.forEach(receiver => receiver.reject(error));
64
+ }
65
+ const generation = (notificationGenerations.get(deviceId) ?? 0) + 1;
66
+ notificationGenerations.set(deviceId, generation);
67
+ const state = {
68
+ generation,
69
+ queue: [],
70
+ pendingReceivers: new Set(),
71
+ };
72
+ notificationStates.set(deviceId, state);
73
+ return state;
74
+ }
75
+ function clearNotificationState(deviceId, reason) {
76
+ const state = notificationStates.get(deviceId);
77
+ if (!state)
78
+ return;
79
+ notificationStates.delete(deviceId);
80
+ const error = new Error(reason);
81
+ state.pendingReceivers.forEach(receiver => receiver.reject(error));
82
+ state.pendingReceivers.clear();
83
+ state.queue.length = 0;
84
+ }
85
+ function waitForNobleCleanup(registerCallback) {
86
+ return new Promise(resolve => {
87
+ let completed = false;
88
+ const complete = () => {
89
+ if (completed)
90
+ return;
91
+ completed = true;
92
+ clearTimeout(timeout);
93
+ resolve();
94
+ };
95
+ const timeout = setTimeout(complete, BLE_CLEANUP_TIMEOUT);
96
+ try {
97
+ registerCallback(complete);
98
+ }
99
+ catch {
100
+ complete();
101
+ }
102
+ });
103
+ }
104
+ async function initializeNoble() {
105
+ if (!noble) {
106
+ try {
107
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
108
+ noble = require('@stoprocent/noble');
109
+ }
110
+ catch (error) {
111
+ throw hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleUnsupported, error instanceof Error ? error.message : String(error));
112
+ }
113
+ }
114
+ if (noble.state === 'poweredOn')
115
+ return;
116
+ if (nobleReadyPromise) {
117
+ await nobleReadyPromise;
118
+ return;
119
+ }
120
+ nobleReadyPromise = new Promise((resolve, reject) => {
121
+ const timeout = setTimeout(() => {
122
+ noble?.removeListener('stateChange', onStateChange);
123
+ reject(hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BlePoweredOff, 'Bluetooth is not powered on'));
124
+ }, BLUETOOTH_INIT_TIMEOUT);
125
+ const onStateChange = (state) => {
126
+ if (state === 'poweredOn') {
127
+ clearTimeout(timeout);
128
+ noble?.removeListener('stateChange', onStateChange);
129
+ resolve();
130
+ }
131
+ else if (state === 'unsupported') {
132
+ clearTimeout(timeout);
133
+ noble?.removeListener('stateChange', onStateChange);
134
+ reject(hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleUnsupported));
135
+ }
136
+ };
137
+ noble?.on('stateChange', onStateChange);
138
+ }).finally(() => {
139
+ nobleReadyPromise = null;
140
+ });
141
+ await nobleReadyPromise;
142
+ }
143
+ function stopScanning() {
144
+ try {
145
+ noble?.stopScanning();
146
+ }
147
+ catch {
148
+ // ignore best-effort scan cleanup
149
+ }
150
+ }
151
+ async function scanDevices(targetDeviceId) {
152
+ await initializeNoble();
153
+ if (!noble) {
154
+ throw hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.RuntimeError, 'Noble not initialized');
155
+ }
156
+ if (!targetDeviceId) {
157
+ discoveredDevices.clear();
158
+ }
159
+ const nobleInstance = noble;
160
+ return new Promise((resolve, reject) => {
161
+ const found = new Map();
162
+ const cleanup = () => {
163
+ clearTimeout(timeout);
164
+ nobleInstance.removeListener('discover', onDiscover);
165
+ stopScanning();
166
+ };
167
+ const finish = () => {
168
+ cleanup();
169
+ resolve([...found.values()]);
170
+ };
171
+ const onDiscover = (peripheral) => {
172
+ if (targetDeviceId && peripheral.id !== targetDeviceId)
173
+ return;
174
+ if (!targetDeviceId && !isOneKeyPeripheral(peripheral))
175
+ return;
176
+ discoveredDevices.set(peripheral.id, peripheral);
177
+ found.set(peripheral.id, peripheral);
178
+ if (targetDeviceId) {
179
+ finish();
180
+ }
181
+ };
182
+ const timeout = setTimeout(finish, DEVICE_SCAN_TIMEOUT);
183
+ nobleInstance.on('discover', onDiscover);
184
+ nobleInstance.startScanning([], false, (error) => {
185
+ if (error) {
186
+ cleanup();
187
+ reject(hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleScanError, error.message));
188
+ }
189
+ });
190
+ });
191
+ }
192
+ function connectPeripheral(peripheral) {
193
+ if (peripheral.state === 'connected')
194
+ return Promise.resolve();
195
+ return new Promise((resolve, reject) => {
196
+ const timeout = setTimeout(() => {
197
+ reject(hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleConnectedError, 'Connection timeout'));
198
+ }, CONNECTION_TIMEOUT);
199
+ peripheral.connect((error) => {
200
+ clearTimeout(timeout);
201
+ if (error) {
202
+ reject(hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleConnectedError, error.message));
203
+ return;
204
+ }
205
+ resolve();
206
+ });
207
+ });
208
+ }
209
+ async function discoverCharacteristics(peripheral) {
210
+ const services = await new Promise((resolve, reject) => {
211
+ const timeout = setTimeout(() => {
212
+ reject(hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleServiceNotFound, 'Service discovery timeout'));
213
+ }, SERVICE_DISCOVERY_TIMEOUT);
214
+ peripheral.discoverServices([], (error, discoveredServices) => {
215
+ clearTimeout(timeout);
216
+ if (error) {
217
+ reject(hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleServiceNotFound, error.message));
218
+ return;
219
+ }
220
+ resolve(discoveredServices);
221
+ });
222
+ });
223
+ let service = services.find(s => NORMALIZED_ONEKEY_SERVICE_UUIDS.has(getBleUuidKey(s.uuid)));
224
+ if (!service) {
225
+ service =
226
+ services.find(s => PRO2_ADVERTISEMENT_SERVICE_UUID_KEYS.has(getBleUuidKey(s.uuid))) ||
227
+ services.find(s => !isGenericBleService(s.uuid)) ||
228
+ services[0];
229
+ }
230
+ if (!service) {
231
+ throw hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleServiceNotFound, 'No BLE service found');
232
+ }
233
+ const selectedService = service;
234
+ const characteristics = await new Promise((resolve, reject) => {
235
+ selectedService.discoverCharacteristics([], (error, discoveredCharacteristics) => {
236
+ if (error) {
237
+ reject(hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleCharacteristicNotFound, error.message));
238
+ return;
239
+ }
240
+ resolve(discoveredCharacteristics);
241
+ });
242
+ });
243
+ let writeCharacteristic;
244
+ let notifyCharacteristic;
245
+ for (const characteristic of characteristics) {
246
+ const uuidKey = getBleUuidKey(characteristic.uuid);
247
+ if (uuidKey === NORMALIZED_WRITE_UUID) {
248
+ writeCharacteristic = characteristic;
249
+ }
250
+ else if (uuidKey === NORMALIZED_NOTIFY_UUID) {
251
+ notifyCharacteristic = characteristic;
252
+ }
253
+ }
254
+ if (!writeCharacteristic || !notifyCharacteristic) {
255
+ throw hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleCharacteristicNotFound, 'Required OneKey BLE characteristics not found');
256
+ }
257
+ return {
258
+ write: writeCharacteristic,
259
+ notify: notifyCharacteristic,
260
+ };
261
+ }
262
+ function subscribeNotifications(deviceId, generation, notifyCharacteristic) {
263
+ return waitForNobleCleanup(callback => notifyCharacteristic.unsubscribe(callback))
264
+ .then(() => new Promise((resolve, reject) => {
265
+ notifyCharacteristic.subscribe((error) => {
266
+ if (error) {
267
+ const errorMessage = error.message || String(error);
268
+ if (BLE_ENCRYPTION_ERROR_PATTERNS.some(pattern => pattern.test(errorMessage))) {
269
+ reject(hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleDeviceNotBonded, `BLE device ${deviceId} is not paired or the encrypted link is not ready: ${errorMessage}`));
270
+ return;
271
+ }
272
+ reject(hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleCharacteristicNotifyChangeFailure, `Failed to subscribe notifications for ${deviceId}: ${errorMessage}`));
273
+ return;
274
+ }
275
+ resolve();
276
+ });
277
+ }))
278
+ .then(() => {
279
+ notifyCharacteristic.removeAllListeners('data');
280
+ notifyCharacteristic.on('data', data => enqueueNotification(deviceId, generation, data));
281
+ })
282
+ .catch(error => {
283
+ notifyCharacteristic.removeAllListeners('data');
284
+ if (error) {
285
+ throw error;
286
+ }
287
+ throw hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleCharacteristicNotifyChangeFailure);
288
+ });
289
+ }
290
+ function writeCharacteristic(characteristic, buffer, withoutResponse) {
291
+ return new Promise((resolve, reject) => {
292
+ characteristic.write(buffer, withoutResponse, (error) => {
293
+ if (error) {
294
+ reject(error);
295
+ return;
296
+ }
297
+ resolve();
298
+ });
299
+ });
300
+ }
301
+ async function disconnectDevice(uuid) {
302
+ const peripheral = connectedDevices.get(uuid);
303
+ const characteristics = deviceCharacteristics.get(uuid);
304
+ clearNotificationState(uuid, `BLE device disconnected: ${uuid}`);
305
+ if (characteristics) {
306
+ characteristics.notify.removeAllListeners('data');
307
+ await waitForNobleCleanup(callback => characteristics.notify.unsubscribe(callback));
308
+ }
309
+ connectedDevices.delete(uuid);
310
+ deviceCharacteristics.delete(uuid);
311
+ if (!peripheral || peripheral.state === 'disconnected')
312
+ return;
313
+ await waitForNobleCleanup(callback => peripheral.disconnect(callback));
314
+ }
315
+ function createNobleBlePlugin() {
316
+ return {
317
+ version: 'OneKey-CLI-Noble-1.0',
318
+ async init() {
319
+ await initializeNoble();
320
+ },
321
+ async enumerate() {
322
+ const devices = await scanDevices();
323
+ return devices.map(device => ({
324
+ commType: 'ble',
325
+ id: device.id,
326
+ name: device.advertisement?.localName || 'Unknown BLE Device',
327
+ }));
328
+ },
329
+ async connect(uuid) {
330
+ let peripheral = discoveredDevices.get(uuid);
331
+ if (!peripheral) {
332
+ [peripheral] = await scanDevices(uuid);
333
+ }
334
+ if (!peripheral) {
335
+ throw hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.DeviceNotFound, `BLE device not found: ${uuid}`);
336
+ }
337
+ await connectPeripheral(peripheral);
338
+ const characteristics = await discoverCharacteristics(peripheral);
339
+ const notificationState = createNotificationState(uuid);
340
+ try {
341
+ await subscribeNotifications(uuid, notificationState.generation, characteristics.notify);
342
+ }
343
+ catch (error) {
344
+ clearNotificationState(uuid, `BLE notification subscription failed: ${uuid}`);
345
+ throw error;
346
+ }
347
+ connectedDevices.set(uuid, peripheral);
348
+ deviceCharacteristics.set(uuid, characteristics);
349
+ },
350
+ async disconnect(uuid) {
351
+ await disconnectDevice(uuid);
352
+ },
353
+ async send(uuid, data, options) {
354
+ const characteristics = deviceCharacteristics.get(uuid);
355
+ if (!characteristics) {
356
+ throw hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.BleCharacteristicNotFound, `BLE device is not connected: ${uuid}`);
357
+ }
358
+ const buffer = Buffer.from(data, 'hex');
359
+ const withoutResponse = options?.withoutResponse ?? true;
360
+ for (let offset = 0; offset < buffer.length; offset += BLE_PACKET_SIZE) {
361
+ const chunk = buffer.subarray(offset, Math.min(offset + BLE_PACKET_SIZE, buffer.length));
362
+ await writeCharacteristic(characteristics.write, chunk, withoutResponse);
363
+ }
364
+ },
365
+ async receive(uuid) {
366
+ const resolvedUuid = uuid ??
367
+ (notificationStates.size === 1 ? notificationStates.keys().next().value : undefined);
368
+ if (!resolvedUuid) {
369
+ throw hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.RuntimeError, 'BLE receive requires a device UUID when multiple devices are connected');
370
+ }
371
+ const state = notificationStates.get(resolvedUuid);
372
+ if (!state) {
373
+ throw hd_shared_1.ERRORS.TypedError(hd_shared_1.HardwareErrorCode.TransportNotFound, `BLE notification state not found: ${resolvedUuid}`);
374
+ }
375
+ const queued = state.queue.shift();
376
+ if (queued !== undefined)
377
+ return queued;
378
+ return new Promise((resolve, reject) => {
379
+ state.pendingReceivers.add({ resolve, reject });
380
+ });
381
+ },
382
+ };
383
+ }
384
+ exports.createNobleBlePlugin = createNobleBlePlugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hardware-cli",
3
- "version": "1.2.0-alpha.2",
3
+ "version": "1.2.0-alpha.21",
4
4
  "description": "OneKey hardware wallet CLI for testing device communication",
5
5
  "author": "OneKey",
6
6
  "license": "Apache-2.0",
@@ -23,6 +23,7 @@
23
23
  "start": "node dist/cli.js",
24
24
  "search": "node dist/cli.js search",
25
25
  "get-features": "node dist/cli.js get-features",
26
+ "get-state": "node dist/cli.js get-state",
26
27
  "get-address": "node dist/cli.js get-address",
27
28
  "ping": "node dist/cli.js ping",
28
29
  "lint": "eslint .",
@@ -30,11 +31,12 @@
30
31
  "test": "jest"
31
32
  },
32
33
  "dependencies": {
33
- "@onekeyfe/hd-common-connect-sdk": "1.2.0-alpha.2",
34
- "@onekeyfe/hd-core": "1.2.0-alpha.2",
35
- "@onekeyfe/hd-shared": "1.2.0-alpha.2",
36
- "@onekeyfe/hd-transport-usb": "1.2.0-alpha.2",
34
+ "@onekeyfe/hd-common-connect-sdk": "1.2.0-alpha.21",
35
+ "@onekeyfe/hd-core": "1.2.0-alpha.21",
36
+ "@onekeyfe/hd-shared": "1.2.0-alpha.21",
37
+ "@onekeyfe/hd-transport-usb": "1.2.0-alpha.21",
38
+ "@stoprocent/noble": "2.3.16",
37
39
  "commander": "^12.0.0"
38
40
  },
39
- "gitHead": "6c183b7f7027f4b4d3db4f427f6360351a6c6680"
41
+ "gitHead": "efe594367fb3b196a5126baee7e2aba3e94986cd"
40
42
  }
@@ -0,0 +1,8 @@
1
+ import packageJson from '../../package.json';
2
+ import { program } from '../cli';
3
+
4
+ describe('CLI 版本', () => {
5
+ test('与发布包 package.json 使用同一个版本来源', () => {
6
+ expect(program.version()).toBe(packageJson.version);
7
+ });
8
+ });
@@ -0,0 +1,29 @@
1
+ import { selectSearchDevice } from '../deviceSelection';
2
+
3
+ describe('selectSearchDevice', () => {
4
+ it('多设备环境优先选择显式 connectId 对应的设备', () => {
5
+ const devices = [
6
+ { connectId: 'first-device', name: 'Pro A9CA' },
7
+ { connectId: 'target-device', name: 'Pro2 6136' },
8
+ ];
9
+
10
+ expect(selectSearchDevice(devices, 'target-device')).toEqual(devices[1]);
11
+ });
12
+
13
+ it('未指定 connectId 时保持选择第一台设备的兼容行为', () => {
14
+ const devices = [
15
+ { connectId: 'first-device', name: 'Pro2 6136' },
16
+ { connectId: 'second-device', name: 'Pro2 C445' },
17
+ ];
18
+
19
+ expect(selectSearchDevice(devices)).toEqual(devices[0]);
20
+ });
21
+
22
+ it('显式 connectId 暂未出现在扫描结果时仍保留该目标', () => {
23
+ const devices = [{ connectId: 'other-device', name: 'Pro2 C445' }];
24
+
25
+ expect(selectSearchDevice(devices, 'target-device')).toEqual({
26
+ connectId: 'target-device',
27
+ });
28
+ });
29
+ });