@onekeyfe/hd-core 1.2.0-alpha.48 → 1.2.0-alpha.49

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.
Files changed (55) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +4 -4
  2. package/__tests__/device-lifecycle-events.test.ts +105 -3
  3. package/__tests__/method-protocol-support.test.ts +19 -0
  4. package/__tests__/protocol-binding.test.ts +89 -0
  5. package/__tests__/protocol-v2.test.ts +274 -6
  6. package/__tests__/search-devices.test.ts +12 -3
  7. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  8. package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
  9. package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
  10. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  11. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  12. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  13. package/dist/api/FirmwareUpdateV4.d.ts +1 -0
  14. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  15. package/dist/api/SearchDevices.d.ts.map +1 -1
  16. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  17. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  18. package/dist/api/index.d.ts +1 -0
  19. package/dist/api/index.d.ts.map +1 -1
  20. package/dist/core/index.d.ts.map +1 -1
  21. package/dist/device/Device.d.ts +5 -1
  22. package/dist/device/Device.d.ts.map +1 -1
  23. package/dist/device/DevicePool.d.ts.map +1 -1
  24. package/dist/index.d.ts +19 -2
  25. package/dist/index.js +238 -105
  26. package/dist/inject.d.ts +6 -1
  27. package/dist/inject.d.ts.map +1 -1
  28. package/dist/lowLevelInject.d.ts.map +1 -1
  29. package/dist/topLevelInject.d.ts.map +1 -1
  30. package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
  31. package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
  32. package/dist/types/api/index.d.ts +4 -0
  33. package/dist/types/api/index.d.ts.map +1 -1
  34. package/dist/types/params.d.ts +1 -0
  35. package/dist/types/params.d.ts.map +1 -1
  36. package/package.json +4 -4
  37. package/src/api/CheckAllFirmwareRelease.ts +5 -3
  38. package/src/api/DetectDeviceConnectProtocol.ts +18 -0
  39. package/src/api/FirmwareUpdate.ts +6 -2
  40. package/src/api/FirmwareUpdateV2.ts +5 -2
  41. package/src/api/FirmwareUpdateV3.ts +6 -2
  42. package/src/api/FirmwareUpdateV4.ts +68 -40
  43. package/src/api/SearchDevices.ts +3 -1
  44. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +15 -4
  45. package/src/api/firmware/uploadFirmware.ts +17 -4
  46. package/src/api/index.ts +1 -0
  47. package/src/core/index.ts +11 -2
  48. package/src/device/Device.ts +53 -15
  49. package/src/device/DevicePool.ts +7 -2
  50. package/src/inject.ts +57 -2
  51. package/src/lowLevelInject.ts +6 -3
  52. package/src/topLevelInject.ts +6 -3
  53. package/src/types/api/detectDeviceConnectProtocol.ts +7 -0
  54. package/src/types/api/index.ts +11 -0
  55. package/src/types/params.ts +7 -1
@@ -291,7 +291,7 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
291
291
  expect(result.targetsToUpdate).toEqual(['app_v1', 'app_v2']);
292
292
  });
293
293
 
294
- test('uses the existing method on Protocol V2 and keeps its public API name', async () => {
294
+ test('does not read vol0 resource inventory while Protocol V2 is in Application mode', async () => {
295
295
  const method = new CheckAllFirmwareRelease({
296
296
  id: 1,
297
297
  payload: {
@@ -325,13 +325,13 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
325
325
  protocol: 'V2',
326
326
  deviceType: 'pro2',
327
327
  status: 'required',
328
- resourceStatus: 'outdated',
329
- targetsToUpdate: ['boot', 'app_v1', 'resource'],
328
+ resourceStatus: 'unknown',
329
+ targetsToUpdate: ['boot', 'app_v1'],
330
330
  firmware: {
331
331
  status: 'required',
332
332
  },
333
333
  });
334
- expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
334
+ expect(typedCall).not.toHaveBeenCalled();
335
335
  expect(method.getSupportedProtocols()).toEqual(['V1', 'V2']);
336
336
  });
337
337
 
@@ -1,3 +1,4 @@
1
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
1
2
  import { TRANSPORT_EVENT } from '@onekeyfe/hd-transport';
2
3
 
3
4
  import { initConnector, initCore } from '../src/core';
@@ -95,7 +96,7 @@ describe('public device lifecycle events', () => {
95
96
  expect(messages.filter(message => message.type === DEVICE.DISCONNECT)).toHaveLength(1);
96
97
  });
97
98
 
98
- test('uses a cached protocol as a fallback-capable acquire hint', async () => {
99
+ test('uses a verified cached protocol as a strict expected protocol', async () => {
99
100
  jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
100
101
  const device = Device.fromDescriptor({
101
102
  id: 'ble-id',
@@ -103,16 +104,117 @@ describe('public device lifecycle events', () => {
103
104
  commType: 'ble',
104
105
  protocolType: 'V1',
105
106
  } as never);
106
- const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id', protocolType: 'V2' });
107
+ const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id', protocolType: 'V1' });
107
108
  device.deviceConnector = { acquire } as never;
108
109
 
109
110
  await device.acquire();
110
111
 
111
- expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, undefined, 'V1');
112
+ expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, 'V1', undefined);
113
+ expect(device.getProtocol()).toBe('V1');
114
+ expect(device.originalDescriptor.protocolType).toBe('V1');
115
+ });
116
+
117
+ test('bypasses a cached protocol only for explicit active detection', async () => {
118
+ jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
119
+ const device = Device.fromDescriptor({
120
+ id: 'ble-id',
121
+ path: 'ble-id',
122
+ commType: 'ble',
123
+ protocolType: 'V1',
124
+ } as never);
125
+ const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id', protocolType: 'V2' });
126
+ device.deviceConnector = { acquire } as never;
127
+
128
+ await device.acquire(undefined, { forceProtocolDetection: true });
129
+
130
+ expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, undefined, undefined);
112
131
  expect(device.getProtocol()).toBe('V2');
113
132
  expect(device.originalDescriptor.protocolType).toBe('V2');
114
133
  });
115
134
 
135
+ test('does not reuse a stale protocol when active detection returns no protocol', async () => {
136
+ jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
137
+ const device = Device.fromDescriptor({
138
+ id: 'ble-id',
139
+ path: 'ble-id',
140
+ commType: 'ble',
141
+ protocolType: 'V1',
142
+ } as never);
143
+ const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id' });
144
+ const release = jest.fn().mockResolvedValue(undefined);
145
+ device.deviceConnector = { acquire, release } as never;
146
+ await expect(
147
+ device.acquire(undefined, {
148
+ forceProtocolDetection: true,
149
+ throwOnRunPromiseError: true,
150
+ })
151
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.RuntimeError });
152
+ expect(device.originalDescriptor.protocolType).toBe('V1');
153
+ expect(device.hasDeviceAcquire()).toBe(false);
154
+ expect(release).toHaveBeenCalledWith('ble-id', false);
155
+ });
156
+
157
+ test('actively reacquires an already connected device without initializing it', async () => {
158
+ jest.spyOn(DataManager, 'getSettings').mockReturnValue('webusb' as never);
159
+ const device = Device.fromDescriptor({
160
+ id: 'usb-device',
161
+ path: 'usb-device',
162
+ session: 'old-session',
163
+ protocolType: 'V1',
164
+ } as never);
165
+ device.mainId = 'old-session';
166
+ device.commands = { disposed: false } as never;
167
+ const release = jest.spyOn(device, 'release').mockImplementation(() => {
168
+ device.mainId = null;
169
+ device.originalDescriptor.session = null;
170
+ device.commands.disposed = true;
171
+ return Promise.resolve();
172
+ });
173
+ const acquire = jest.spyOn(device, 'acquire').mockImplementation(() => {
174
+ device.mainId = 'new-session';
175
+ device.originalDescriptor.session = 'new-session';
176
+ device.originalDescriptor.protocolType = 'V2';
177
+ device.commands.disposed = false;
178
+ return Promise.resolve();
179
+ });
180
+ const initialize = jest.spyOn(device, 'initialize').mockResolvedValue(undefined);
181
+
182
+ await device.run(() => Promise.resolve(undefined), {
183
+ forceProtocolDetection: true,
184
+ skipInitialize: true,
185
+ });
186
+
187
+ expect(release).toHaveBeenCalled();
188
+ expect(acquire).toHaveBeenCalledWith(undefined, {
189
+ forceProtocolDetection: true,
190
+ });
191
+ expect(initialize).not.toHaveBeenCalled();
192
+ expect(device.getProtocol()).toBe('V2');
193
+ });
194
+
195
+ test('keeps the freshly probed React Native BLE session until the call completes', async () => {
196
+ jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
197
+ const device = Device.fromDescriptor({
198
+ id: 'ble-device',
199
+ path: 'ble-device',
200
+ commType: 'ble',
201
+ protocolType: 'V1',
202
+ } as never);
203
+ device.mainId = 'ble-device';
204
+ device.commands = { disposed: false } as never;
205
+ (device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
206
+ const release = jest.spyOn(device, 'release').mockResolvedValue(undefined);
207
+ const run = jest.fn().mockResolvedValue(undefined);
208
+
209
+ await device.run(run, {
210
+ forceProtocolDetection: true,
211
+ skipInitialize: true,
212
+ });
213
+
214
+ expect(run).toHaveBeenCalledTimes(1);
215
+ expect(release).toHaveBeenCalledTimes(1);
216
+ });
217
+
116
218
  test('keeps an explicit connectProtocol as a strict expected protocol', async () => {
117
219
  jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
118
220
  const device = Device.fromDescriptor({
@@ -1,6 +1,7 @@
1
1
  import { EFirmwareType, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import { BaseMethod } from '../src/api/BaseMethod';
4
+ import { findMethod } from '../src/api/utils';
4
5
  import BTCGetAddress from '../src/api/btc/BTCGetAddress';
5
6
  import DeviceReboot from '../src/api/protocol-v2/DeviceReboot';
6
7
  import SolGetAddress from '../src/api/solana/SolGetAddress';
@@ -20,6 +21,24 @@ class DefaultMethod extends BaseMethod {
20
21
  }
21
22
 
22
23
  describe('method protocol support', () => {
24
+ test('creates a protocol detection method that returns only the current protocol', async () => {
25
+ const method = findMethod({
26
+ id: 6,
27
+ payload: {
28
+ method: 'detectDeviceConnectProtocol',
29
+ connectId: 'ble-device',
30
+ },
31
+ } as never);
32
+
33
+ method.init();
34
+ (method as unknown as { device: unknown }).device = {
35
+ getProtocol: () => 'V2',
36
+ };
37
+
38
+ expect(method.getSupportedProtocols()).toEqual(['V1', 'V2']);
39
+ await expect(method.run()).resolves.toBe('V2');
40
+ });
41
+
23
42
  test('defaults existing methods to Protocol V1 only', () => {
24
43
  const method = new DefaultMethod({
25
44
  id: 1,
@@ -0,0 +1,89 @@
1
+ import { createCoreApi, createProtocolAwareCall } from '../src/inject';
2
+
3
+ import type { CoreApi } from '../src/types/api';
4
+
5
+ describe('SDK protocol binding', () => {
6
+ test('exposes a minimal API that returns the actively detected protocol', async () => {
7
+ const response = { success: true as const, payload: 'V2' as const };
8
+ const rawCall = jest.fn().mockResolvedValue(response);
9
+ const api = createCoreApi(rawCall as CoreApi['call']);
10
+
11
+ await expect(api.detectDeviceConnectProtocol('USB_DEVICE_A')).resolves.toBe(response);
12
+ expect(rawCall).toHaveBeenCalledWith({
13
+ connectId: 'USB_DEVICE_A',
14
+ forceProtocolDetection: true,
15
+ method: 'detectDeviceConnectProtocol',
16
+ });
17
+ });
18
+
19
+ test('injects a strict protocol into every later call and isolates devices', async () => {
20
+ const rawCall = jest.fn(params => Promise.resolve({ success: true, payload: params }));
21
+ const protocolAwareCall = createProtocolAwareCall(rawCall as CoreApi['call']);
22
+ const api = createCoreApi(protocolAwareCall.call);
23
+
24
+ protocolAwareCall.setDeviceConnectProtocol('USB_DEVICE_A', 'V2');
25
+ protocolAwareCall.setDeviceConnectProtocol('ble-device-b', 'V1');
26
+
27
+ await api.deviceWipe('usb_device_a');
28
+ await api.deviceSupportFeatures('BLE-DEVICE-B');
29
+ await api.getFeatures('USB_DEVICE_A', { connectProtocol: 'V1' });
30
+ await api.getDeviceState('USB_DEVICE_A', { forceProtocolDetection: true });
31
+ await protocolAwareCall.call({
32
+ connectId: 'USB_DEVICE_A',
33
+ method: 'deviceUpdateReboot',
34
+ });
35
+
36
+ expect(rawCall).toHaveBeenNthCalledWith(1, {
37
+ connectId: 'usb_device_a',
38
+ method: 'deviceWipe',
39
+ connectProtocol: 'V2',
40
+ });
41
+ expect(rawCall).toHaveBeenNthCalledWith(2, {
42
+ connectId: 'BLE-DEVICE-B',
43
+ method: 'deviceSupportFeatures',
44
+ connectProtocol: 'V1',
45
+ });
46
+ expect(rawCall).toHaveBeenNthCalledWith(3, {
47
+ connectId: 'USB_DEVICE_A',
48
+ method: 'getFeatures',
49
+ connectProtocol: 'V1',
50
+ });
51
+ expect(rawCall).toHaveBeenNthCalledWith(4, {
52
+ connectId: 'USB_DEVICE_A',
53
+ method: 'getDeviceState',
54
+ forceProtocolDetection: true,
55
+ });
56
+ expect(rawCall).toHaveBeenNthCalledWith(5, {
57
+ connectId: 'USB_DEVICE_A',
58
+ method: 'deviceUpdateReboot',
59
+ connectProtocol: 'V2',
60
+ });
61
+ });
62
+
63
+ test('keeps bindings local to one SDK instance and supports clearing them', async () => {
64
+ const firstRawCall = jest.fn(() => Promise.resolve({ success: true, payload: {} }));
65
+ const secondRawCall = jest.fn(() => Promise.resolve({ success: true, payload: {} }));
66
+ const first = createProtocolAwareCall(firstRawCall as CoreApi['call']);
67
+ const second = createProtocolAwareCall(secondRawCall as CoreApi['call']);
68
+
69
+ first.setDeviceConnectProtocol('same-device', 'V2');
70
+ await first.call({ connectId: 'same-device', method: 'deviceWipe' });
71
+ await second.call({ connectId: 'same-device', method: 'deviceWipe' });
72
+ first.setDeviceConnectProtocol('same-device', undefined);
73
+ await first.call({ connectId: 'same-device', method: 'deviceWipe' });
74
+
75
+ expect(firstRawCall).toHaveBeenNthCalledWith(1, {
76
+ connectId: 'same-device',
77
+ method: 'deviceWipe',
78
+ connectProtocol: 'V2',
79
+ });
80
+ expect(secondRawCall).toHaveBeenCalledWith({
81
+ connectId: 'same-device',
82
+ method: 'deviceWipe',
83
+ });
84
+ expect(firstRawCall).toHaveBeenNthCalledWith(2, {
85
+ connectId: 'same-device',
86
+ method: 'deviceWipe',
87
+ });
88
+ });
89
+ });
@@ -3923,6 +3923,68 @@ describe('Protocol V2 firmware update targets', () => {
3923
3923
  });
3924
3924
  });
3925
3925
 
3926
+ test('enters Protocol V2 bootloader before reading and downloading remote resources', async () => {
3927
+ const method = new FirmwareUpdateV4({
3928
+ id: 1,
3929
+ payload: {
3930
+ method: 'firmwareUpdateV4',
3931
+ platform: 'web',
3932
+ targetsToUpdate: ['resource'],
3933
+ },
3934
+ });
3935
+ method.init();
3936
+
3937
+ (method as any).device = stubDevice({
3938
+ originalDescriptor: { id: 'usb-id', path: 'app-path', protocolType: 'V2' },
3939
+ features: {
3940
+ deviceType: 'pro2',
3941
+ firmwareVersion: '1.0.0',
3942
+ mode: 'normal',
3943
+ bootloaderMode: false,
3944
+ capabilities: [],
3945
+ },
3946
+ isBootloader: () => false,
3947
+ isRomloader: () => false,
3948
+ });
3949
+ (method as any).captureProtocolV2PhysicalIdentity = jest.fn().mockResolvedValue(undefined);
3950
+ (method as any).prepareRemoteProtocolV2Binaries = jest.fn().mockResolvedValue({
3951
+ bootloaderBinary: null,
3952
+ fwBinaryMap: [],
3953
+ installItems: [],
3954
+ });
3955
+
3956
+ const order: string[] = [];
3957
+ (method as any).enterProtocolV2BootloaderMode = jest.fn().mockImplementation(() => {
3958
+ order.push('enter-bootloader');
3959
+ return Promise.resolve(true);
3960
+ });
3961
+ (method as any).prepareProtocolV2ResourceBundles = jest.fn().mockImplementation(() => {
3962
+ order.push('prepare-resources');
3963
+ return Promise.resolve([
3964
+ {
3965
+ name: 'images.okpkg',
3966
+ binary: new Uint8Array([1]).buffer,
3967
+ devicePath: 'vol0:/bundles/images/images.okpkg',
3968
+ },
3969
+ ]);
3970
+ });
3971
+ (method as any).executeProtocolV2Update = jest.fn().mockImplementation(() => {
3972
+ order.push('execute-update');
3973
+ return Promise.resolve();
3974
+ });
3975
+ (method as any).exitProtocolV2BootloaderToNormal = jest.fn().mockResolvedValue(undefined);
3976
+ (method as any).waitForProtocolV2FinalFeatures = jest.fn().mockResolvedValue({
3977
+ bootloaderVersion: '1.0.0',
3978
+ bleVersion: '1.0.0',
3979
+ firmwareVersion: '1.0.0',
3980
+ });
3981
+ method.postTipMessage = jest.fn();
3982
+
3983
+ await method.run();
3984
+
3985
+ expect(order).toEqual(['enter-bootloader', 'prepare-resources', 'execute-update']);
3986
+ });
3987
+
3926
3988
  test('reboots Protocol V2 normal-mode device to bootloader before transfer', async () => {
3927
3989
  const method = new FirmwareUpdateV4({
3928
3990
  id: 1,
@@ -3972,6 +4034,33 @@ describe('Protocol V2 firmware update targets', () => {
3972
4034
  expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(1);
3973
4035
  });
3974
4036
 
4037
+ test('keeps Protocol V2 bootloader active before firmware transfer', async () => {
4038
+ const method = new FirmwareUpdateV4({
4039
+ id: 1,
4040
+ payload: {
4041
+ method: 'firmwareUpdateV4',
4042
+ },
4043
+ });
4044
+ (method as any).device = stubDevice({
4045
+ originalDescriptor: { id: 'usb-id', path: 'bootloader-path', protocolType: 'V2' },
4046
+ features: {
4047
+ deviceType: 'pro2',
4048
+ mode: 'bootloader',
4049
+ bootloaderMode: true,
4050
+ capabilities: [],
4051
+ },
4052
+ isBootloader: () => true,
4053
+ isRomloader: () => false,
4054
+ });
4055
+ (method as any).protocolV2Reboot = jest.fn();
4056
+ method.postTipMessage = jest.fn();
4057
+
4058
+ await expect((method as any).enterProtocolV2BootloaderMode()).resolves.toBe(false);
4059
+
4060
+ expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
4061
+ expect(method.postTipMessage).not.toHaveBeenCalledWith('AutoRebootToBootloader');
4062
+ });
4063
+
3975
4064
  test('keeps Protocol V2 romloader active before firmware transfer', async () => {
3976
4065
  const method = new FirmwareUpdateV4({
3977
4066
  id: 1,
@@ -4039,7 +4128,7 @@ describe('Protocol V2 firmware update targets', () => {
4039
4128
 
4040
4129
  await method.run();
4041
4130
 
4042
- expect(prepareResourceBundles).toHaveBeenCalledWith(true);
4131
+ expect(prepareResourceBundles).not.toHaveBeenCalled();
4043
4132
  expect((method as any).protocolV2Reboot).not.toHaveBeenCalled();
4044
4133
  expect((method as any).executeProtocolV2Update).toHaveBeenCalledWith({
4045
4134
  bootloaderBinary: null,
@@ -4122,7 +4211,10 @@ describe('Protocol V2 firmware update targets', () => {
4122
4211
  getCommands: () => commands,
4123
4212
  mainId: 'usb-path',
4124
4213
  });
4125
- const cachedDevice = { getConnectId: () => 'usb-path' };
4214
+ const cachedDevice = {
4215
+ getConnectId: () => 'usb-path',
4216
+ getCurrentSerialNo: () => '',
4217
+ };
4126
4218
  const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
4127
4219
  devices: {},
4128
4220
  deviceList: [cachedDevice],
@@ -4139,6 +4231,182 @@ describe('Protocol V2 firmware update targets', () => {
4139
4231
  expect(initialize).not.toHaveBeenCalled();
4140
4232
  });
4141
4233
 
4234
+ test('selects the matching physical device from multiple Protocol V2 USB reconnect candidates', async () => {
4235
+ const method = new FirmwareUpdateV4({
4236
+ id: 1,
4237
+ payload: {
4238
+ method: 'firmwareUpdateV4',
4239
+ },
4240
+ });
4241
+ const commands = { disposed: true, mainId: '' };
4242
+ const updateFromCache = jest.fn();
4243
+ const device = stubDevice({
4244
+ originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
4245
+ deviceConnector: {
4246
+ enumerate: jest.fn().mockResolvedValue({
4247
+ descriptors: [
4248
+ { id: 'usb-a', path: 'usb-a', protocolType: 'V2' },
4249
+ { id: 'usb-b', path: 'usb-b', protocolType: 'V2' },
4250
+ ],
4251
+ }),
4252
+ },
4253
+ updateFromCache,
4254
+ hasDeviceAcquire: jest.fn(() => false),
4255
+ acquire: jest.fn().mockResolvedValue(undefined),
4256
+ commands,
4257
+ getCommands: () => commands,
4258
+ mainId: 'old-usb-path',
4259
+ });
4260
+ const otherDevice = {
4261
+ getConnectId: () => 'usb-a',
4262
+ getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
4263
+ };
4264
+ const expectedDevice = {
4265
+ getConnectId: () => 'usb-b',
4266
+ getCurrentSerialNo: () => 'PRO2-PHYSICAL-B',
4267
+ };
4268
+ const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
4269
+ devices: {},
4270
+ deviceList: [otherDevice, expectedDevice],
4271
+ } as any);
4272
+ (method as any).device = device;
4273
+ (method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-B';
4274
+
4275
+ try {
4276
+ await (method as any).reconnectProtocolV2Device();
4277
+ } finally {
4278
+ getDevices.mockRestore();
4279
+ }
4280
+
4281
+ expect(updateFromCache).toHaveBeenCalledWith(expectedDevice);
4282
+ });
4283
+
4284
+ test('rejects ambiguous Protocol V2 USB reconnect candidates without an identity match', async () => {
4285
+ const method = new FirmwareUpdateV4({
4286
+ id: 1,
4287
+ payload: {
4288
+ method: 'firmwareUpdateV4',
4289
+ },
4290
+ });
4291
+ const updateFromCache = jest.fn();
4292
+ const device = stubDevice({
4293
+ originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
4294
+ deviceConnector: {
4295
+ enumerate: jest.fn().mockResolvedValue({
4296
+ descriptors: [
4297
+ { id: 'usb-a', path: 'usb-a', protocolType: 'V2' },
4298
+ { id: 'usb-b', path: 'usb-b', protocolType: 'V2' },
4299
+ ],
4300
+ }),
4301
+ },
4302
+ updateFromCache,
4303
+ });
4304
+ const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
4305
+ devices: {},
4306
+ deviceList: [
4307
+ {
4308
+ getConnectId: () => 'usb-a',
4309
+ getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
4310
+ },
4311
+ {
4312
+ getConnectId: () => 'usb-b',
4313
+ getCurrentSerialNo: () => 'PRO2-PHYSICAL-B',
4314
+ },
4315
+ ],
4316
+ } as any);
4317
+ (method as any).device = device;
4318
+ (method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-C';
4319
+
4320
+ try {
4321
+ await expect((method as any).reconnectProtocolV2Device()).rejects.toBeDefined();
4322
+ } finally {
4323
+ getDevices.mockRestore();
4324
+ }
4325
+
4326
+ expect(updateFromCache).not.toHaveBeenCalled();
4327
+ });
4328
+
4329
+ test('allows a single Protocol V2 USB reconnect candidate when its physical serial is unavailable', async () => {
4330
+ const method = new FirmwareUpdateV4({
4331
+ id: 1,
4332
+ payload: {
4333
+ method: 'firmwareUpdateV4',
4334
+ },
4335
+ });
4336
+ const commands = { disposed: true, mainId: '' };
4337
+ const updateFromCache = jest.fn();
4338
+ const device = stubDevice({
4339
+ originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
4340
+ deviceConnector: {
4341
+ enumerate: jest.fn().mockResolvedValue({
4342
+ descriptors: [{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' }],
4343
+ }),
4344
+ },
4345
+ updateFromCache,
4346
+ hasDeviceAcquire: jest.fn(() => false),
4347
+ acquire: jest.fn().mockResolvedValue(undefined),
4348
+ commands,
4349
+ getCommands: () => commands,
4350
+ mainId: 'old-usb-path',
4351
+ });
4352
+ const candidate = {
4353
+ getConnectId: () => 'usb-a',
4354
+ getCurrentSerialNo: () => '',
4355
+ };
4356
+ const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
4357
+ devices: {},
4358
+ deviceList: [candidate],
4359
+ } as any);
4360
+ (method as any).device = device;
4361
+ (method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-A';
4362
+
4363
+ try {
4364
+ await (method as any).reconnectProtocolV2Device();
4365
+ } finally {
4366
+ getDevices.mockRestore();
4367
+ }
4368
+
4369
+ expect(updateFromCache).toHaveBeenCalledWith(candidate);
4370
+ });
4371
+
4372
+ test('rejects a single Protocol V2 USB reconnect candidate with a different physical serial', async () => {
4373
+ const method = new FirmwareUpdateV4({
4374
+ id: 1,
4375
+ payload: {
4376
+ method: 'firmwareUpdateV4',
4377
+ },
4378
+ });
4379
+ const updateFromCache = jest.fn();
4380
+ const device = stubDevice({
4381
+ originalDescriptor: { id: 'old-usb-id', path: 'old-usb-path', protocolType: 'V2' },
4382
+ deviceConnector: {
4383
+ enumerate: jest.fn().mockResolvedValue({
4384
+ descriptors: [{ id: 'usb-a', path: 'usb-a', protocolType: 'V2' }],
4385
+ }),
4386
+ },
4387
+ updateFromCache,
4388
+ });
4389
+ const getDevices = jest.spyOn(DevicePool, 'getDevices').mockResolvedValue({
4390
+ devices: {},
4391
+ deviceList: [
4392
+ {
4393
+ getConnectId: () => 'usb-a',
4394
+ getCurrentSerialNo: () => 'PRO2-PHYSICAL-A',
4395
+ },
4396
+ ],
4397
+ } as any);
4398
+ (method as any).device = device;
4399
+ (method as any).protocolV2ExpectedSerialNumber = 'PRO2-PHYSICAL-B';
4400
+
4401
+ try {
4402
+ await expect((method as any).reconnectProtocolV2Device()).rejects.toBeDefined();
4403
+ } finally {
4404
+ getDevices.mockRestore();
4405
+ }
4406
+
4407
+ expect(updateFromCache).not.toHaveBeenCalled();
4408
+ });
4409
+
4142
4410
  test('reuses an acquired Protocol V2 USB session while polling reconnect readiness', async () => {
4143
4411
  const method = new FirmwareUpdateV4({
4144
4412
  id: 1,
@@ -5938,14 +6206,14 @@ describe('Protocol V2 firmware reconnect identity', () => {
5938
6206
  .mockReturnValue(stable as any);
5939
6207
  (method as any).downloadProtocolV2Resource = jest.fn();
5940
6208
 
5941
- await expect((method as any).prepareProtocolV2ResourceBundles(false)).resolves.toEqual([]);
6209
+ await expect((method as any).prepareProtocolV2ResourceBundles()).resolves.toEqual([]);
5942
6210
  expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
5943
6211
  expect(typedCall.mock.calls.some(call => call[0] === 'FilesystemFileRead')).toBe(true);
5944
6212
  expect((method as any).downloadProtocolV2Resource).not.toHaveBeenCalled();
5945
6213
  resourcesSpy.mockRestore();
5946
6214
  });
5947
6215
 
5948
- test('downloads only changed resources in Application mode', async () => {
6216
+ test('downloads only changed resources from loader inventory', async () => {
5949
6217
  const method = new FirmwareUpdateV4({
5950
6218
  id: 1,
5951
6219
  payload: { method: 'firmwareUpdateV4', platform: 'web', targetsToUpdate: ['resource'] },
@@ -5973,7 +6241,7 @@ describe('Protocol V2 firmware reconnect identity', () => {
5973
6241
  })
5974
6242
  );
5975
6243
 
5976
- const bundles = await (method as any).prepareProtocolV2ResourceBundles(false);
6244
+ const bundles = await (method as any).prepareProtocolV2ResourceBundles();
5977
6245
 
5978
6246
  expect(bundles).toHaveLength(1);
5979
6247
  expect((method as any).downloadProtocolV2Resource).toHaveBeenCalledWith(stable[0]);
@@ -6008,7 +6276,7 @@ describe('Protocol V2 firmware reconnect identity', () => {
6008
6276
  })
6009
6277
  );
6010
6278
 
6011
- const bundles = await (method as any).prepareProtocolV2ResourceBundles(true);
6279
+ const bundles = await (method as any).prepareProtocolV2ResourceBundles();
6012
6280
 
6013
6281
  expect(bundles).toHaveLength(1);
6014
6282
  expect(typedCall.mock.calls.some(call => call[0] === 'ResourceInventoryGet')).toBe(false);
@@ -42,7 +42,7 @@ describe('SearchDevices', () => {
42
42
  mockIsBleConnect.mockReturnValue(false);
43
43
  });
44
44
 
45
- test('单个 USB 描述符无响应时跳过该设备并继续搜索', async () => {
45
+ test('搜索忽略调用方协议并主动探测,单个无响应设备不阻断后续结果', async () => {
46
46
  const unresponsiveDescriptor = {
47
47
  path: 'stale-usb-device',
48
48
  id: 'stale-usb-device',
@@ -74,6 +74,7 @@ describe('SearchDevices', () => {
74
74
  id: 1,
75
75
  payload: {
76
76
  method: 'searchDevices',
77
+ connectProtocol: 'V2',
77
78
  },
78
79
  } as any);
79
80
  method.init();
@@ -89,13 +90,21 @@ describe('SearchDevices', () => {
89
90
  1,
90
91
  [unresponsiveDescriptor],
91
92
  unresponsiveDescriptor.path,
92
- { connectProtocol: undefined, refreshRuntimeState: true }
93
+ {
94
+ connectProtocol: undefined,
95
+ forceProtocolDetection: true,
96
+ refreshRuntimeState: true,
97
+ }
93
98
  );
94
99
  expect(mockGetDevices).toHaveBeenNthCalledWith(
95
100
  2,
96
101
  [availableDescriptor],
97
102
  availableDescriptor.path,
98
- { connectProtocol: undefined, refreshRuntimeState: true }
103
+ {
104
+ connectProtocol: undefined,
105
+ forceProtocolDetection: true,
106
+ refreshRuntimeState: true,
107
+ }
99
108
  );
100
109
  });
101
110
 
@@ -1 +1 @@
1
- {"version":3,"file":"CheckAllFirmwareRelease.d.ts","sourceRoot":"","sources":["../../src/api/CheckAllFirmwareRelease.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAA6B,MAAM,qBAAqB,CAAC;AAY/E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM1C,OAAO,KAAK,EACV,kBAAkB,EAElB,kCAAkC,EAClC,+BAA+B,EAChC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EAGrB,MAAM,UAAU,CAAC;AA6HlB,KAAK,6BAA6B,GAAG;IACnC,YAAY,EAAE,aAAa,CAAC;IAC5B,MAAM,EAAE,+BAA+B,CAAC;IACxC,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,mBAAmB,CAAC;IACrC,UAAU,EAAE,kCAAkC,EAAE,CAAC;IACjD,eAAe,EAAE,sBAAsB,EAAE,CAAC;IAC1C,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;CAC3C,CAAC;AAEF,wBAAgB,8BAA8B,CAAC,EAC7C,eAAe,EACf,mBAAwB,EACxB,mBAAwB,EACxB,YAAY,EACZ,OAAO,GACR,EAAE;IACD,eAAe,EAAE,mBAAmB,CAAC;IACrC,mBAAmB,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACpD,mBAAmB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IAC9D,YAAY,EAAE,aAAa,CAAC;IAC5B,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;CAC3C,GAAG,6BAA6B,CAoEhC;AAED,MAAM,CAAC,OAAO,OAAO,uBAAwB,SAAQ,UAAU;IAC7D,qBAAqB;IAIrB,IAAI;IAME,GAAG;YAuDK,aAAa;CAmF5B"}
1
+ {"version":3,"file":"CheckAllFirmwareRelease.d.ts","sourceRoot":"","sources":["../../src/api/CheckAllFirmwareRelease.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAA6B,MAAM,qBAAqB,CAAC;AAY/E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM1C,OAAO,KAAK,EACV,kBAAkB,EAElB,kCAAkC,EAClC,+BAA+B,EAChC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EAGrB,MAAM,UAAU,CAAC;AA6HlB,KAAK,6BAA6B,GAAG;IACnC,YAAY,EAAE,aAAa,CAAC;IAC5B,MAAM,EAAE,+BAA+B,CAAC;IACxC,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,mBAAmB,CAAC;IACrC,UAAU,EAAE,kCAAkC,EAAE,CAAC;IACjD,eAAe,EAAE,sBAAsB,EAAE,CAAC;IAC1C,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;CAC3C,CAAC;AAEF,wBAAgB,8BAA8B,CAAC,EAC7C,eAAe,EACf,mBAAwB,EACxB,mBAAwB,EACxB,YAAY,EACZ,OAAO,GACR,EAAE;IACD,eAAe,EAAE,mBAAmB,CAAC;IACrC,mBAAmB,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACpD,mBAAmB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IAC9D,YAAY,EAAE,aAAa,CAAC;IAC5B,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;CAC3C,GAAG,6BAA6B,CAoEhC;AAED,MAAM,CAAC,OAAO,OAAO,uBAAwB,SAAQ,UAAU;IAC7D,qBAAqB;IAIrB,IAAI;IAME,GAAG;YAuDK,aAAa;CAqF5B"}
@@ -0,0 +1,7 @@
1
+ import { BaseMethod } from './BaseMethod';
2
+ export default class DetectDeviceConnectProtocol extends BaseMethod {
3
+ init(): void;
4
+ getSupportedProtocols(): readonly ["V1", "V2"];
5
+ run(): Promise<"V1" | "V2">;
6
+ }
7
+ //# sourceMappingURL=DetectDeviceConnectProtocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DetectDeviceConnectProtocol.d.ts","sourceRoot":"","sources":["../../src/api/DetectDeviceConnectProtocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,MAAM,CAAC,OAAO,OAAO,2BAA4B,SAAQ,UAAU;IACjE,IAAI;IAOJ,qBAAqB;IAIrB,GAAG;CAGJ"}