@onekeyfe/hd-core 1.2.0-alpha.174 → 1.2.0-alpha.176

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.
@@ -14,7 +14,7 @@ describe('FirmwareUpdateV4 install polling', () => {
14
14
  jest.restoreAllMocks();
15
15
  });
16
16
 
17
- test('keeps USB on Stage, Request Success, then status polling', async () => {
17
+ test('writes the USB Request before polling current install status', async () => {
18
18
  const method = new FirmwareUpdateV4({
19
19
  id: 1,
20
20
  payload: {
@@ -26,7 +26,22 @@ describe('FirmwareUpdateV4 install polling', () => {
26
26
  const typedCall = jest
27
27
  .fn()
28
28
  .mockResolvedValueOnce({ type: 'Success', message: {} })
29
- .mockResolvedValueOnce({ type: 'Success', message: {} })
29
+ .mockResolvedValueOnce({
30
+ type: 'DeviceFirmwareUpdateStatus',
31
+ message: { records: [] },
32
+ })
33
+ .mockResolvedValueOnce({
34
+ type: 'DeviceFirmwareUpdateStatus',
35
+ message: {
36
+ records: [
37
+ {
38
+ target_id: 4,
39
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_PENDING',
40
+ path: 'vol0:/application_p1.bin',
41
+ },
42
+ ],
43
+ },
44
+ })
30
45
  .mockResolvedValueOnce({
31
46
  type: 'DeviceFirmwareUpdateStatus',
32
47
  message: {
@@ -39,12 +54,17 @@ describe('FirmwareUpdateV4 install polling', () => {
39
54
  ],
40
55
  },
41
56
  });
57
+ const call = jest.fn().mockResolvedValue({
58
+ type: 'WriteCompleted',
59
+ message: {},
60
+ });
61
+ const setCancelableAction = jest.fn();
42
62
 
43
63
  method.device = {
44
- getCommands: () => ({ typedCall }),
64
+ getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
45
65
  createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
46
66
  toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-usb' }),
47
- setCancelableAction: jest.fn(),
67
+ setCancelableAction,
48
68
  clearCancelableAction: jest.fn(),
49
69
  } as unknown as Device;
50
70
  method.postMessage = jest.fn();
@@ -53,7 +73,10 @@ describe('FirmwareUpdateV4 install polling', () => {
53
73
 
54
74
  const firmwareUpdate = method as unknown as {
55
75
  protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
56
- waitForProtocolV2FirmwareUpdateComplete: (value: typeof targets) => Promise<void>;
76
+ waitForProtocolV2FirmwareUpdateComplete: (
77
+ value: typeof targets,
78
+ requireCurrentInstallStatus: boolean
79
+ ) => Promise<void>;
57
80
  reconnectProtocolV2Device: () => Promise<void>;
58
81
  verifyProtocolV2ReconnectIdentity: () => Promise<Record<string, never>>;
59
82
  };
@@ -62,15 +85,155 @@ describe('FirmwareUpdateV4 install polling', () => {
62
85
  (method as any).isBleReconnect = jest.fn(() => false);
63
86
 
64
87
  await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
65
- await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets);
66
88
 
67
89
  expect(typedCall.mock.calls[0]).toEqual(['DeviceFirmwareUpdateStage', 'Success', { targets }]);
68
- expect(typedCall.mock.calls[1]).toEqual(['DeviceFirmwareUpdateRequest', 'Success', {}]);
69
- expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
70
- expect(typedCall.mock.calls[2]?.[0]).toBe('DeviceFirmwareUpdateStatusGet');
71
- expect(typedCall.mock.invocationCallOrder[1]).toBeLessThan(
72
- typedCall.mock.invocationCallOrder[2]
90
+ expect(call).toHaveBeenCalledWith(
91
+ 'DeviceFirmwareUpdateRequest',
92
+ {},
93
+ expect.objectContaining({
94
+ returnAfterWrite: true,
95
+ expectedTypes: ['Success'],
96
+ onResponseAfterWrite: expect.any(Function),
97
+ })
73
98
  );
99
+ expect(method.postProgressMessage).not.toHaveBeenCalled();
100
+
101
+ await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
102
+
103
+ expect(typedCall.mock.calls[1]?.[0]).toBe('DeviceFirmwareUpdateStatusGet');
104
+ expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
105
+ expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
106
+ expect(call.mock.invocationCallOrder[0]).toBeLessThan(typedCall.mock.invocationCallOrder[1]);
107
+ expect(setCancelableAction).toHaveBeenCalledTimes(2);
108
+ });
109
+
110
+ test('accepts finished USB status after the Request terminal Success arrives', async () => {
111
+ const method = new FirmwareUpdateV4({
112
+ id: 1,
113
+ payload: {
114
+ method: 'firmwareUpdateV4',
115
+ connectId: 'pro2-usb',
116
+ },
117
+ });
118
+ const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
119
+ const typedCall = jest
120
+ .fn()
121
+ .mockResolvedValueOnce({ type: 'Success', message: {} })
122
+ .mockResolvedValueOnce({
123
+ type: 'DeviceFirmwareUpdateStatus',
124
+ message: {
125
+ records: [
126
+ {
127
+ target_id: 4,
128
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
129
+ path: 'vol0:/application_p1.bin',
130
+ },
131
+ ],
132
+ },
133
+ });
134
+ const call = jest.fn().mockResolvedValue({
135
+ type: 'WriteCompleted',
136
+ message: {},
137
+ });
138
+
139
+ method.device = {
140
+ getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
141
+ createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
142
+ toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-usb' }),
143
+ setCancelableAction: jest.fn(),
144
+ clearCancelableAction: jest.fn(),
145
+ } as unknown as Device;
146
+ method.postMessage = jest.fn();
147
+ method.postProgressMessage = jest.fn();
148
+
149
+ const firmwareUpdate = method as unknown as {
150
+ protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
151
+ waitForProtocolV2FirmwareUpdateComplete: (
152
+ value: typeof targets,
153
+ requireCurrentInstallStatus: boolean
154
+ ) => Promise<void>;
155
+ };
156
+
157
+ await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
158
+ const requestOptions = call.mock.calls[0]?.[2] as {
159
+ onResponseAfterWrite: (response: { type: 'Success'; message: Record<string, never> }) => void;
160
+ };
161
+ requestOptions.onResponseAfterWrite({ type: 'Success', message: {} });
162
+ await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
163
+
164
+ expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
165
+ });
166
+
167
+ test('reconnects USB when the link is released while writing the Request', async () => {
168
+ const method = new FirmwareUpdateV4({
169
+ id: 1,
170
+ payload: {
171
+ method: 'firmwareUpdateV4',
172
+ connectId: 'pro2-usb',
173
+ },
174
+ });
175
+ const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
176
+ const typedCall = jest
177
+ .fn()
178
+ .mockResolvedValueOnce({ type: 'Success', message: {} })
179
+ .mockResolvedValueOnce({
180
+ type: 'DeviceFirmwareUpdateStatus',
181
+ message: {
182
+ records: [
183
+ {
184
+ target_id: 4,
185
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_IN_PROGRESS',
186
+ path: 'vol0:/application_p1.bin',
187
+ },
188
+ ],
189
+ },
190
+ })
191
+ .mockResolvedValueOnce({
192
+ type: 'DeviceFirmwareUpdateStatus',
193
+ message: {
194
+ records: [
195
+ {
196
+ target_id: 4,
197
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
198
+ path: 'vol0:/application_p1.bin',
199
+ },
200
+ ],
201
+ },
202
+ });
203
+ const call = jest.fn().mockRejectedValue(new Error('device was disconnected'));
204
+
205
+ method.device = {
206
+ getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
207
+ createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
208
+ toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-usb' }),
209
+ setCancelableAction: jest.fn(),
210
+ clearCancelableAction: jest.fn(),
211
+ } as unknown as Device;
212
+ method.postMessage = jest.fn();
213
+ method.postProgressMessage = jest.fn();
214
+
215
+ const firmwareUpdate = method as unknown as {
216
+ protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
217
+ waitForProtocolV2FirmwareUpdateComplete: (
218
+ value: typeof targets,
219
+ requireCurrentInstallStatus: boolean
220
+ ) => Promise<void>;
221
+ reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
222
+ verifyProtocolV2ReconnectIdentity: () => Promise<Record<string, never>>;
223
+ };
224
+ firmwareUpdate.reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
225
+ firmwareUpdate.verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue({});
226
+ (method as any).isBleReconnect = jest.fn(() => false);
227
+
228
+ await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
229
+ await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
230
+
231
+ expect(firmwareUpdate.reconnectProtocolV2Device).toHaveBeenCalledWith({
232
+ skipProtocolProbe: true,
233
+ });
234
+ expect(firmwareUpdate.verifyProtocolV2ReconnectIdentity).toHaveBeenCalledTimes(1);
235
+ expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
236
+ expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
74
237
  });
75
238
 
76
239
  test('writes the BLE Request and completes only from target status polling', async () => {
@@ -155,6 +318,67 @@ describe('FirmwareUpdateV4 install polling', () => {
155
318
  expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
156
319
  });
157
320
 
321
+ test('confirms App mode when BLE status becomes empty after current install progress', async () => {
322
+ const method = new FirmwareUpdateV4({
323
+ id: 1,
324
+ payload: {
325
+ method: 'firmwareUpdateV4',
326
+ connectId: 'pro2-ble',
327
+ },
328
+ });
329
+ const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
330
+ const typedCall = jest
331
+ .fn()
332
+ .mockResolvedValueOnce({
333
+ type: 'DeviceFirmwareUpdateStatus',
334
+ message: {
335
+ records: [
336
+ {
337
+ target_id: 4,
338
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_IN_PROGRESS',
339
+ path: 'vol0:/application_p1.bin',
340
+ },
341
+ ],
342
+ },
343
+ })
344
+ .mockResolvedValueOnce({
345
+ type: 'DeviceFirmwareUpdateStatus',
346
+ message: { records: [] },
347
+ });
348
+ const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
349
+ const deviceInfo = {};
350
+ const verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
351
+ const probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
352
+
353
+ method.device = {
354
+ getCommands: () => ({ typedCall }),
355
+ } as unknown as Device;
356
+ method.postProgressMessage = jest.fn();
357
+
358
+ const firmwareUpdate = method as unknown as {
359
+ protocolV2InstallNeedsReconnect: boolean;
360
+ waitForProtocolV2FirmwareUpdateComplete: (
361
+ value: typeof targets,
362
+ requireCurrentInstallStatus: boolean
363
+ ) => Promise<void>;
364
+ reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
365
+ verifyProtocolV2ReconnectIdentity: () => Promise<typeof deviceInfo>;
366
+ probeProtocolV2NormalMode: (value: typeof deviceInfo) => Promise<boolean>;
367
+ };
368
+ firmwareUpdate.protocolV2InstallNeedsReconnect = true;
369
+ firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
370
+ firmwareUpdate.verifyProtocolV2ReconnectIdentity = verifyProtocolV2ReconnectIdentity;
371
+ firmwareUpdate.probeProtocolV2NormalMode = probeProtocolV2NormalMode;
372
+ (method as any).isBleReconnect = jest.fn(() => true);
373
+
374
+ await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
375
+
376
+ expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
377
+ expect(verifyProtocolV2ReconnectIdentity).toHaveBeenCalledTimes(1);
378
+ expect(probeProtocolV2NormalMode).toHaveBeenCalledWith(deviceInfo);
379
+ expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
380
+ });
381
+
158
382
  test('does not send Request when Stage is rejected', async () => {
159
383
  const method = new FirmwareUpdateV4({
160
384
  id: 1,
@@ -192,40 +416,46 @@ describe('FirmwareUpdateV4 install polling', () => {
192
416
  const targets = [{ target_id: 4, path: 'vol0:/application_p1.bin' }];
193
417
  const actionCancelledError = ERRORS.TypedError(HardwareErrorCode.ActionCancelled);
194
418
  const typedCall = jest.fn().mockImplementation((type: string) => {
195
- if (type === 'DeviceFirmwareUpdateRequest') {
196
- return Promise.reject(actionCancelledError);
197
- }
198
419
  if (type === 'DeviceFirmwareUpdateStatusGet') {
199
- return Promise.resolve({ type: 'Success', message: {} });
420
+ return Promise.reject(actionCancelledError);
200
421
  }
201
422
  return Promise.resolve({ type: 'Success', message: {} });
202
423
  });
203
424
  const call = jest.fn().mockResolvedValue({
204
- type: 'Failure',
205
- message: { code: 'Failure_ActionCancelled' },
425
+ type: 'WriteCompleted',
426
+ message: {},
206
427
  });
207
428
 
208
429
  method.device = {
209
- getCommands: () => ({ typedCall, call }),
430
+ getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
210
431
  createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
211
432
  toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-usb' }),
212
433
  setCancelableAction: jest.fn(),
434
+ clearCancelableAction: jest.fn(),
213
435
  } as unknown as Device;
214
436
  method.postMessage = jest.fn();
215
437
  method.postProgressMessage = jest.fn();
216
438
 
439
+ const firmwareUpdate = method as unknown as {
440
+ protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
441
+ waitForProtocolV2FirmwareUpdateComplete: (
442
+ value: typeof targets,
443
+ requireCurrentInstallStatus: boolean
444
+ ) => Promise<void>;
445
+ };
446
+
447
+ await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
217
448
  await expect(
218
- (
219
- method as unknown as {
220
- protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
221
- }
222
- ).protocolV2StartFirmwareUpdate({ targets })
449
+ firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true)
223
450
  ).rejects.toMatchObject({
224
451
  errorCode: HardwareErrorCode.ActionCancelled,
225
452
  });
226
453
 
227
- expect(typedCall).toHaveBeenCalledWith('DeviceFirmwareUpdateRequest', 'Success', {});
228
- expect(call).not.toHaveBeenCalled();
454
+ expect(call).toHaveBeenCalledWith(
455
+ 'DeviceFirmwareUpdateRequest',
456
+ {},
457
+ expect.objectContaining({ returnAfterWrite: true })
458
+ );
229
459
  expect(method.postProgressMessage).not.toHaveBeenCalled();
230
460
  });
231
461
 
@@ -5401,6 +5401,7 @@ describe('Protocol V2 firmware update targets', () => {
5401
5401
  });
5402
5402
  const targets = [{ target_id: 4, path: 'vol1:firmware.bin' }];
5403
5403
  const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
5404
+ const call = jest.fn().mockResolvedValue({ type: 'WriteCompleted', message: {} });
5404
5405
  const cancelDevice = jest.fn().mockResolvedValue(undefined);
5405
5406
  let cancelableAction: (() => Promise<unknown>) | undefined;
5406
5407
  const interaction = {
@@ -5413,7 +5414,7 @@ describe('Protocol V2 firmware update targets', () => {
5413
5414
  };
5414
5415
 
5415
5416
  (method as any).device = stubDevice({
5416
- getCommands: () => ({ typedCall, cancelDevice }),
5417
+ getCommands: () => ({ typedCall, call, cancelDevice }),
5417
5418
  createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(interaction),
5418
5419
  toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
5419
5420
  setCancelableAction: jest.fn(action => {
@@ -5442,18 +5443,21 @@ describe('Protocol V2 firmware update targets', () => {
5442
5443
  interaction,
5443
5444
  },
5444
5445
  });
5445
- expect(typedCall).toHaveBeenCalledWith('DeviceFirmwareUpdateRequest', 'Success', {});
5446
- const requestCallIndex = typedCall.mock.calls.findIndex(
5447
- ([type]) => type === 'DeviceFirmwareUpdateRequest'
5448
- );
5449
- expect(typedCall.mock.invocationCallOrder[0]).toBeLessThan(
5450
- typedCall.mock.invocationCallOrder[requestCallIndex]
5446
+ expect(call).toHaveBeenCalledWith(
5447
+ 'DeviceFirmwareUpdateRequest',
5448
+ {},
5449
+ expect.objectContaining({
5450
+ returnAfterWrite: true,
5451
+ expectedTypes: ['Success'],
5452
+ onResponseAfterWrite: expect.any(Function),
5453
+ })
5451
5454
  );
5455
+ expect(typedCall.mock.invocationCallOrder[0]).toBeLessThan(call.mock.invocationCallOrder[0]);
5452
5456
  expect((method.postMessage as jest.Mock).mock.invocationCallOrder[0]).toBeLessThan(
5453
- typedCall.mock.invocationCallOrder[requestCallIndex]
5457
+ call.mock.invocationCallOrder[0]
5454
5458
  );
5455
5459
  expect(method.postTipMessage).not.toHaveBeenCalled();
5456
- expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
5460
+ expect(method.postProgressMessage).not.toHaveBeenCalled();
5457
5461
  await cancelableAction?.();
5458
5462
  expect(cancelDevice).toHaveBeenCalledTimes(1);
5459
5463
  });
@@ -5466,9 +5470,10 @@ describe('Protocol V2 firmware update targets', () => {
5466
5470
  },
5467
5471
  });
5468
5472
  const typedCall = jest.fn().mockRejectedValue(new Error('stage failed'));
5473
+ const call = jest.fn();
5469
5474
 
5470
5475
  (method as any).device = stubDevice({
5471
- getCommands: () => ({ typedCall }),
5476
+ getCommands: () => ({ typedCall, call }),
5472
5477
  });
5473
5478
  method.postMessage = jest.fn();
5474
5479
  method.postTipMessage = jest.fn();
@@ -5480,7 +5485,7 @@ describe('Protocol V2 firmware update targets', () => {
5480
5485
  })
5481
5486
  ).rejects.toThrow('stage failed');
5482
5487
 
5483
- expect(typedCall).not.toHaveBeenCalledWith('DeviceFirmwareUpdateRequest', 'Success', {});
5488
+ expect(call).not.toHaveBeenCalled();
5484
5489
  expect(method.postMessage).not.toHaveBeenCalled();
5485
5490
  expect(method.postTipMessage).not.toHaveBeenCalled();
5486
5491
  expect(method.postProgressMessage).not.toHaveBeenCalled();
@@ -5493,15 +5498,11 @@ describe('Protocol V2 firmware update targets', () => {
5493
5498
  method: 'firmwareUpdateV4',
5494
5499
  },
5495
5500
  });
5496
- const typedCall = jest.fn().mockImplementation((type: string) => {
5497
- if (type === 'DeviceFirmwareUpdateRequest') {
5498
- return Promise.reject(new Error('request failed'));
5499
- }
5500
- return Promise.resolve({ type: 'Success', message: {} });
5501
- });
5501
+ const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
5502
+ const call = jest.fn().mockRejectedValue(new Error('request failed'));
5502
5503
 
5503
5504
  (method as any).device = stubDevice({
5504
- getCommands: () => ({ typedCall }),
5505
+ getCommands: () => ({ typedCall, call }),
5505
5506
  createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
5506
5507
  toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
5507
5508
  });
@@ -5515,7 +5516,15 @@ describe('Protocol V2 firmware update targets', () => {
5515
5516
  })
5516
5517
  ).rejects.toThrow('request failed');
5517
5518
 
5518
- expect(typedCall).toHaveBeenCalledWith('DeviceFirmwareUpdateRequest', 'Success', {});
5519
+ expect(call).toHaveBeenCalledWith(
5520
+ 'DeviceFirmwareUpdateRequest',
5521
+ {},
5522
+ expect.objectContaining({
5523
+ returnAfterWrite: true,
5524
+ expectedTypes: ['Success'],
5525
+ onResponseAfterWrite: expect.any(Function),
5526
+ })
5527
+ );
5519
5528
  expect(method.postTipMessage).not.toHaveBeenCalled();
5520
5529
  expect(method.postProgressMessage).not.toHaveBeenCalled();
5521
5530
  });
@@ -5554,7 +5563,7 @@ describe('Protocol V2 firmware update targets', () => {
5554
5563
  })
5555
5564
  );
5556
5565
  expect(typedCall).not.toHaveBeenCalledWith('DeviceFirmwareUpdateRequest', 'Success', {});
5557
- expect((method as any).protocolV2InstallNeedsBleReconnect).toBe(true);
5566
+ expect((method as any).protocolV2InstallNeedsReconnect).toBe(true);
5558
5567
  });
5559
5568
 
5560
5569
  test.each([
@@ -5594,7 +5603,7 @@ describe('Protocol V2 firmware update targets', () => {
5594
5603
  getCommands: () => ({ typedCall }),
5595
5604
  });
5596
5605
  (method as any).isBleReconnect = jest.fn(() => true);
5597
- (method as any).protocolV2InstallNeedsBleReconnect = true;
5606
+ (method as any).protocolV2InstallNeedsReconnect = true;
5598
5607
  (method as any).reconnectProtocolV2Device = reconnectProtocolV2Device;
5599
5608
  (method as any).verifyProtocolV2ReconnectIdentity = verifyProtocolV2ReconnectIdentity;
5600
5609
  method.postProgressMessage = jest.fn();
@@ -6150,7 +6159,7 @@ describe('Protocol V2 firmware update targets', () => {
6150
6159
  (method as any).device = stubDevice({
6151
6160
  getCommands: () => ({ typedCall }),
6152
6161
  });
6153
- (method as any).protocolV2InstallRequestConfirmed = true;
6162
+ (method as any).protocolV2InstallTerminalSuccessObserved = true;
6154
6163
  (method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
6155
6164
  (method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
6156
6165
  (method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
@@ -6196,7 +6205,7 @@ describe('Protocol V2 firmware update targets', () => {
6196
6205
  (method as any).device = stubDevice({
6197
6206
  getCommands: () => ({ typedCall }),
6198
6207
  });
6199
- (method as any).protocolV2InstallRequestConfirmed = true;
6208
+ (method as any).protocolV2InstallTerminalSuccessObserved = true;
6200
6209
  (method as any).reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
6201
6210
  (method as any).verifyProtocolV2ReconnectIdentity = jest.fn().mockResolvedValue(deviceInfo);
6202
6211
  (method as any).probeProtocolV2NormalMode = jest.fn().mockResolvedValue(true);
@@ -9087,27 +9096,28 @@ describe('Protocol V2 firmware update targets', () => {
9087
9096
  expect(writePayloads.map(payload => payload.file.data.byteLength)).toEqual([1960, 1]);
9088
9097
  });
9089
9098
 
9090
- test('keeps confirmation active until the empty Request responds, then enters install', async () => {
9099
+ test('keeps confirmation active after the Request write until terminal Success arrives', async () => {
9091
9100
  const method = new FirmwareUpdateV4({
9092
9101
  id: 1,
9093
9102
  payload: {
9094
9103
  method: 'firmwareUpdateV4',
9095
9104
  },
9096
9105
  });
9097
- let resolveRequest: ((value: unknown) => void) | undefined;
9098
- const typedCall = jest.fn().mockImplementation((type: string) => {
9099
- if (type === 'DeviceFirmwareUpdateRequest') {
9100
- return new Promise(resolve => {
9101
- resolveRequest = resolve;
9102
- });
9103
- }
9104
- return Promise.resolve({ type: 'Success', message: {} });
9106
+ let onResponseAfterWrite:
9107
+ | ((response: { type: string; message: Record<string, never> }) => void)
9108
+ | undefined;
9109
+ const typedCall = jest.fn().mockResolvedValue({ type: 'Success', message: {} });
9110
+ const call = jest.fn().mockImplementation((_type, _message, options) => {
9111
+ onResponseAfterWrite = options.onResponseAfterWrite;
9112
+ return Promise.resolve({ type: 'WriteCompleted', message: {} });
9105
9113
  });
9114
+ const clearCancelableAction = jest.fn();
9106
9115
 
9107
9116
  (method as any).device = stubDevice({
9108
- getCommands: () => ({ typedCall }),
9117
+ getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
9109
9118
  createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
9110
9119
  toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
9120
+ clearCancelableAction,
9111
9121
  });
9112
9122
  method.postMessage = jest.fn();
9113
9123
  method.postProgressMessage = jest.fn();
@@ -9125,17 +9135,28 @@ describe('Protocol V2 firmware update targets', () => {
9125
9135
  'Success',
9126
9136
  { targets: [{ target_id: 4, path: 'vol1:firmware.bin' }] },
9127
9137
  ]);
9128
- expect(typedCall).toHaveBeenCalledWith('DeviceFirmwareUpdateRequest', 'Success', {});
9138
+ expect(call).toHaveBeenCalledWith(
9139
+ 'DeviceFirmwareUpdateRequest',
9140
+ {},
9141
+ expect.objectContaining({
9142
+ returnAfterWrite: true,
9143
+ expectedTypes: ['Success'],
9144
+ onResponseAfterWrite: expect.any(Function),
9145
+ })
9146
+ );
9129
9147
  expect(method.postTipMessage).not.toHaveBeenCalled();
9130
9148
  expect(method.postProgressMessage).not.toHaveBeenCalled();
9149
+ expect(clearCancelableAction).not.toHaveBeenCalled();
9131
9150
 
9132
- resolveRequest?.({ type: 'Success', message: {} });
9133
9151
  await startPromise;
9152
+ onResponseAfterWrite?.({ type: 'Success', message: {} });
9153
+ expect(clearCancelableAction).toHaveBeenCalledTimes(1);
9154
+ expect((method as any).protocolV2InstallTerminalSuccessObserved).toBe(true);
9134
9155
  expect(method.postTipMessage).not.toHaveBeenCalled();
9135
- expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
9156
+ expect(method.postProgressMessage).not.toHaveBeenCalled();
9136
9157
  });
9137
9158
 
9138
- test('returns after the empty Protocol V2 firmware install request responds', async () => {
9159
+ test('returns after the empty Protocol V2 firmware install request is written', async () => {
9139
9160
  const method = new FirmwareUpdateV4({
9140
9161
  id: 1,
9141
9162
  payload: {
@@ -9146,9 +9167,13 @@ describe('Protocol V2 firmware update targets', () => {
9146
9167
  type: 'Success',
9147
9168
  message: { message: 'accepted' },
9148
9169
  });
9170
+ const call = jest.fn().mockResolvedValue({
9171
+ type: 'WriteCompleted',
9172
+ message: {},
9173
+ });
9149
9174
 
9150
9175
  (method as any).device = stubDevice({
9151
- getCommands: () => ({ typedCall }),
9176
+ getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
9152
9177
  createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
9153
9178
  toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2' }),
9154
9179
  });
@@ -9161,9 +9186,17 @@ describe('Protocol V2 firmware update targets', () => {
9161
9186
  });
9162
9187
 
9163
9188
  expect(typedCall.mock.calls[0][0]).toBe('DeviceFirmwareUpdateStage');
9164
- expect(typedCall).toHaveBeenCalledWith('DeviceFirmwareUpdateRequest', 'Success', {});
9189
+ expect(call).toHaveBeenCalledWith(
9190
+ 'DeviceFirmwareUpdateRequest',
9191
+ {},
9192
+ expect.objectContaining({
9193
+ returnAfterWrite: true,
9194
+ expectedTypes: ['Success'],
9195
+ onResponseAfterWrite: expect.any(Function),
9196
+ })
9197
+ );
9165
9198
  expect(method.postTipMessage).not.toHaveBeenCalled();
9166
- expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
9199
+ expect(method.postProgressMessage).not.toHaveBeenCalled();
9167
9200
  });
9168
9201
  });
9169
9202
 
@@ -17,8 +17,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
17
17
  private protocolV2LatestFinalFeatures?;
18
18
  private protocolV2LatestFinalDeviceInfo?;
19
19
  private protocolV2InstallBaselineVersions;
20
- private protocolV2InstallNeedsBleReconnect;
21
- private protocolV2InstallRequestConfirmed;
20
+ private protocolV2InstallNeedsReconnect;
21
+ private protocolV2InstallTerminalSuccessObserved;
22
22
  private protocolV2LastRuntimeProbeFeatures?;
23
23
  private protocolV2LastTransferProgress?;
24
24
  private protocolV2LastTransferProgressAt;
@@ -1 +1 @@
1
- {"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":";AACA,OAAO,YAAY,MAAM,QAAQ,CAAC;AAoClC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAsB1C,OAAO,eAAe,MAAM,2BAA2B,CAAC;AAYxD,OAAO,KAAK,EAAE,eAAe,EAAyB,MAAM,UAAU,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAmD,MAAM,WAAW,CAAC;AAI9F,OAAO,KAAK,EACV,6BAA6B,EAG9B,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAWpD,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAmE7D,eAAO,MAAM,OAAO,YAAmB,WAAW,WAAW,WAAW,iBAoFvE,CAAC;AAwqBF,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,WAWpF;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,WAW/E;AAED,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,WAQlF;AAiVD,eAAO,MAAM,MAAM,YAAa,WAAW,cAAc,MAAM,SAuG9D,CAAC;AAmGF,eAAO,MAAM,qBAAqB,gFAejC,CAAC;AAiLF,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,YAAY;IAC5C,OAAO,CAAC,cAAc,CAAoB;IAE1C,SAAgB,aAAa,EAAE,MAAM,CAAC;IAEtC,OAAO,CAAC,YAAY,CAAsB;IAE1C,OAAO,CAAC,cAAc,CAAC,CAAgB;IAGvC,OAAO,CAAC,sBAAsB,CAAoC;IAElE,OAAO,CAAC,iBAAiB,CAAoB;;IAS7C,OAAO,CAAC,cAAc;IA6BhB,aAAa,CAAC,OAAO,EAAE,WAAW;IAuExC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAOV,gBAAgB;CAiC/B;AAED,eAAO,MAAM,QAAQ,YAIpB,CAAC;AAEF,eAAO,MAAM,aAAa,uBAYzB,CAAC;AAMF,eAAO,MAAM,IAAI,aACL,eAAe,aACd,GAAG,WACL,6BAA6B,8BAiBvC,CAAC;AAEF,eAAO,MAAM,eAAe;SAKrB,eAAe,CAAC,KAAK,CAAC;eAChB,GAAG;;UASf,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":";AACA,OAAO,YAAY,MAAM,QAAQ,CAAC;AAoClC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAsB1C,OAAO,eAAe,MAAM,2BAA2B,CAAC;AAYxD,OAAO,KAAK,EAAE,eAAe,EAAyB,MAAM,UAAU,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAmD,MAAM,WAAW,CAAC;AAI9F,OAAO,KAAK,EACV,6BAA6B,EAG9B,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAWpD,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAmE7D,eAAO,MAAM,OAAO,YAAmB,WAAW,WAAW,WAAW,iBAoFvE,CAAC;AAiqBF,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,WAWpF;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,WAW/E;AAED,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,WAQlF;AA0PD,eAAO,MAAM,MAAM,YAAa,WAAW,cAAc,MAAM,SAuG9D,CAAC;AAmGF,eAAO,MAAM,qBAAqB,gFAejC,CAAC;AAiLF,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,YAAY;IAC5C,OAAO,CAAC,cAAc,CAAoB;IAE1C,SAAgB,aAAa,EAAE,MAAM,CAAC;IAEtC,OAAO,CAAC,YAAY,CAAsB;IAE1C,OAAO,CAAC,cAAc,CAAC,CAAgB;IAGvC,OAAO,CAAC,sBAAsB,CAAoC;IAElE,OAAO,CAAC,iBAAiB,CAAoB;;IAS7C,OAAO,CAAC,cAAc;IA6BhB,aAAa,CAAC,OAAO,EAAE,WAAW;IAuExC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAOV,gBAAgB;CAiC/B;AAED,eAAO,MAAM,QAAQ,YAIpB,CAAC;AAEF,eAAO,MAAM,aAAa,uBAYzB,CAAC;AAMF,eAAO,MAAM,IAAI,aACL,eAAe,aACd,GAAG,WACL,6BAA6B,8BAiBvC,CAAC;AAEF,eAAO,MAAM,eAAe;SAKrB,eAAe,CAAC,KAAK,CAAC;eAChB,GAAG;;UASf,CAAC"}
package/dist/index.js CHANGED
@@ -51982,8 +51982,8 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
51982
51982
  this.protocolV2CompletedTargetVersions = new Map();
51983
51983
  this.protocolV2CompletedTargetIds = new Set();
51984
51984
  this.protocolV2InstallBaselineVersions = new Map();
51985
- this.protocolV2InstallNeedsBleReconnect = false;
51986
- this.protocolV2InstallRequestConfirmed = false;
51985
+ this.protocolV2InstallNeedsReconnect = false;
51986
+ this.protocolV2InstallTerminalSuccessObserved = false;
51987
51987
  this.protocolV2LastTransferProgressAt = 0;
51988
51988
  }
51989
51989
  getSupportedProtocols() {
@@ -53417,11 +53417,11 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
53417
53417
  const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
53418
53418
  const startTime = Date.now();
53419
53419
  let lastError;
53420
- let shouldReconnect = this.protocolV2InstallNeedsBleReconnect;
53421
- this.protocolV2InstallNeedsBleReconnect = false;
53420
+ let shouldReconnect = this.protocolV2InstallNeedsReconnect;
53421
+ this.protocolV2InstallNeedsReconnect = false;
53422
53422
  let deviceInfo;
53423
53423
  let bleInstallLinkReady = false;
53424
- let installEvidenceObserved = this.protocolV2InstallRequestConfirmed;
53424
+ let installEvidenceObserved = this.protocolV2InstallTerminalSuccessObserved;
53425
53425
  let currentInstallStatusObserved = false;
53426
53426
  const liveTargetIds = new Set();
53427
53427
  while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
@@ -53436,7 +53436,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
53436
53436
  : yield this.verifyProtocolV2ReconnectIdentity();
53437
53437
  shouldReconnect = false;
53438
53438
  }
53439
- const currentDeviceInfo = deviceInfo;
53439
+ let currentDeviceInfo = deviceInfo;
53440
53440
  try {
53441
53441
  const statusResponse = yield this.device.getCommands().typedCall('DeviceFirmwareUpdateStatusGet', 'DeviceFirmwareUpdateStatus', {
53442
53442
  fields: {
@@ -53445,7 +53445,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
53445
53445
  path: true,
53446
53446
  },
53447
53447
  }, { timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT });
53448
- if (this.protocolV2InstallRequestConfirmed) {
53448
+ if (this.protocolV2InstallTerminalSuccessObserved) {
53449
53449
  installEvidenceObserved = true;
53450
53450
  }
53451
53451
  const statusTargets = ((_a = statusResponse.message.records) !== null && _a !== void 0 ? _a : []);
@@ -53465,16 +53465,29 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
53465
53465
  const targetId = normalizeProtocolV2TargetId(target.target_id);
53466
53466
  return targetId !== undefined && expectedTargetIds.has(targetId);
53467
53467
  });
53468
- const shouldVerifyTargetCompletion = !requireCurrentInstallStatus || currentInstallStatusObserved;
53468
+ const hasCurrentInstallEvidence = currentInstallStatusObserved || this.protocolV2InstallTerminalSuccessObserved;
53469
+ const shouldVerifyTargetCompletion = !requireCurrentInstallStatus || hasCurrentInstallEvidence;
53469
53470
  if (shouldVerifyTargetCompletion &&
53470
- this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds, expectedPaths, requireCurrentInstallStatus ? liveTargetIds : undefined)) {
53471
+ this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds, expectedPaths, requireCurrentInstallStatus && !this.protocolV2InstallTerminalSuccessObserved
53472
+ ? liveTargetIds
53473
+ : undefined)) {
53471
53474
  return;
53472
53475
  }
53476
+ if (!hasCurrentInstallEvidence) {
53477
+ this.device.setCancelableAction(() => this.device.getCommands().cancelDevice());
53478
+ }
53473
53479
  if (requireCurrentInstallStatus &&
53474
- !currentInstallStatusObserved &&
53480
+ !hasCurrentInstallEvidence &&
53475
53481
  matchingStatusTargets.length > 0) {
53476
53482
  lastError = new Error('Protocol V2 firmware status is stale; waiting for the current install to start');
53477
53483
  }
53484
+ if (statusTargets.length === 0 &&
53485
+ !currentDeviceInfo &&
53486
+ bleInstallLinkReady &&
53487
+ installEvidenceObserved) {
53488
+ currentDeviceInfo = yield this.verifyProtocolV2ReconnectIdentity();
53489
+ deviceInfo = currentDeviceInfo;
53490
+ }
53478
53491
  if (statusTargets.length === 0 && currentDeviceInfo) {
53479
53492
  const isNormalMode = yield this.probeProtocolV2NormalMode(currentDeviceInfo);
53480
53493
  if (isNormalMode &&
@@ -53797,54 +53810,34 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
53797
53810
  protocolV2StartFirmwareUpdate({ targets, }) {
53798
53811
  return __awaiter(this, void 0, void 0, function* () {
53799
53812
  this.protocolV2LastRuntimeProbeFeatures = undefined;
53800
- this.protocolV2InstallNeedsBleReconnect = false;
53801
- this.protocolV2InstallRequestConfirmed = false;
53813
+ this.protocolV2InstallNeedsReconnect = false;
53814
+ this.protocolV2InstallTerminalSuccessObserved = false;
53802
53815
  const commands = this.device.getCommands();
53803
53816
  yield commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
53804
- this.device.setCancelableAction(() => commands.cancelDevice());
53817
+ this.device.setCancelableAction(() => this.device.getCommands().cancelDevice());
53805
53818
  const interaction = this.device.createProtocolV2UiPhaseMetadata('button', 'start');
53806
53819
  this.postMessage(createUiMessage(UI_REQUEST.REQUEST_BUTTON, Object.assign({ device: this.device.toMessageObject(), source: 'method-lifecycle', reason: 'firmware-update', completion: 'operation-completed', deviceOnly: true, operation: this.name }, (interaction ? { interaction } : {}))));
53807
- if (this.isBleReconnect()) {
53808
- try {
53809
- yield commands.call('DeviceFirmwareUpdateRequest', {}, {
53810
- returnAfterWrite: true,
53811
- expectedTypes: ['Success'],
53812
- onResponseAfterWrite: response => {
53813
- if (response.type !== 'Success')
53814
- return;
53815
- this.protocolV2InstallRequestConfirmed = true;
53816
- this.device.clearCancelableAction();
53817
- this.postProgressMessage(1, 'installingFirmware');
53818
- Log$7.log('[FirmwareUpdateV4] BLE firmware install confirmed by device response');
53819
- },
53820
- });
53821
- Log$7.log('[FirmwareUpdateV4] BLE firmware install request written; continue with status polling');
53822
- }
53823
- catch (error) {
53824
- this.throwIfAborted();
53825
- if (!isProtocolV2DeviceDisconnectedError(error)) {
53826
- throw error;
53827
- }
53828
- Log$7.log('[FirmwareUpdateV4] BLE transport released while writing install request; continue status polling');
53829
- this.protocolV2InstallNeedsBleReconnect = true;
53830
- }
53831
- return;
53832
- }
53833
53820
  try {
53834
- yield commands.typedCall('DeviceFirmwareUpdateRequest', 'Success', {});
53835
- this.protocolV2InstallRequestConfirmed = true;
53836
- this.postProgressMessage(1, 'installingFirmware');
53837
- Log$7.log('[FirmwareUpdateV4] firmware install confirmed by device response');
53821
+ yield commands.call('DeviceFirmwareUpdateRequest', {}, {
53822
+ returnAfterWrite: true,
53823
+ expectedTypes: ['Success'],
53824
+ onResponseAfterWrite: response => {
53825
+ if (response.type !== 'Success')
53826
+ return;
53827
+ this.protocolV2InstallTerminalSuccessObserved = true;
53828
+ this.device.clearCancelableAction();
53829
+ Log$7.log('[FirmwareUpdateV4] firmware install completed by device response');
53830
+ },
53831
+ });
53832
+ Log$7.log('[FirmwareUpdateV4] firmware install request written; continue with status polling');
53838
53833
  }
53839
53834
  catch (error) {
53840
53835
  this.throwIfAborted();
53841
53836
  if (!isProtocolV2DeviceDisconnectedError(error)) {
53842
53837
  throw error;
53843
53838
  }
53844
- Log$7.log('[FirmwareUpdateV4] BLE transport released after install request; continue status polling');
53845
- if (this.isBleReconnect()) {
53846
- this.protocolV2InstallNeedsBleReconnect = true;
53847
- }
53839
+ Log$7.log('[FirmwareUpdateV4] transport released while writing install request; continue status polling');
53840
+ this.protocolV2InstallNeedsReconnect = true;
53848
53841
  }
53849
53842
  });
53850
53843
  }
@@ -65191,8 +65184,6 @@ const onCallDevice = (context, message, method) => __awaiter(void 0, void 0, voi
65191
65184
  if ((_g = method.payload) === null || _g === void 0 ? void 0 : _g.onlyConnectBleDevice) {
65192
65185
  preWarmCallbackTask === null || preWarmCallbackTask === void 0 ? void 0 : preWarmCallbackTask.resolve();
65193
65186
  Log.debug('Call API - only connect ble device: ', device === null || device === void 0 ? void 0 : device.mainId);
65194
- completeMethodRequestContext(method);
65195
- requestQueue.releaseTask(method.responseID);
65196
65187
  return createResponseMessage(method.responseID, true, null);
65197
65188
  }
65198
65189
  Log.debug('Call API - setDevice: ', device.mainId);
@@ -65568,31 +65559,7 @@ function isMissingDetectedProtocolV2Error(method, error) {
65568
65559
  typeof typedError.message === 'string' &&
65569
65560
  typedError.message.includes('Device protocol has not been detected'));
65570
65561
  }
65571
- const BLE_ACQUIRE_DEADLINE_MS = 60 * 1000;
65572
- function raceBleAcquire(acquirePromise, abortSignal) {
65573
- return new Promise((resolve, reject) => {
65574
- let settled = false;
65575
- const settle = (fn) => {
65576
- if (settled)
65577
- return;
65578
- settled = true;
65579
- clearTimeout(deadline);
65580
- abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.removeEventListener('abort', onAbort);
65581
- fn();
65582
- };
65583
- const onAbort = () => settle(() => reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.CallQueueActionCancelled)));
65584
- const deadline = setTimeout(() => settle(() => reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE acquire exceeded ${BLE_ACQUIRE_DEADLINE_MS}ms deadline`))), BLE_ACQUIRE_DEADLINE_MS);
65585
- acquirePromise.then(value => settle(() => resolve(value)), error => settle(() => reject(error)));
65586
- if (abortSignal) {
65587
- if (abortSignal.aborted) {
65588
- onAbort();
65589
- return;
65590
- }
65591
- abortSignal.addEventListener('abort', onAbort);
65592
- }
65593
- });
65594
- }
65595
- function connectDeviceForBle(method, device, abortSignal, retryCount = 0) {
65562
+ function connectDeviceForBle(method, device, retryCount = 0) {
65596
65563
  var _a;
65597
65564
  return __awaiter(this, void 0, void 0, function* () {
65598
65565
  try {
@@ -65607,31 +65574,9 @@ function connectDeviceForBle(method, device, abortSignal, retryCount = 0) {
65607
65574
  !device.commands ||
65608
65575
  device.commands.disposed;
65609
65576
  if (shouldAcquire) {
65610
- const useAcquireGuards = DataManager.getSettings('env') === 'desktop-web-ble';
65611
- if (useAcquireGuards && (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted)) {
65612
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.CallQueueActionCancelled);
65613
- }
65614
- if (!useAcquireGuards) {
65615
- yield device.acquire(method.payload.connectProtocol, {
65616
- forceProtocolDetection: method.payload.forceProtocolDetection,
65617
- });
65618
- }
65619
- else {
65620
- try {
65621
- yield raceBleAcquire(device.acquire(method.payload.connectProtocol, {
65622
- forceProtocolDetection: method.payload.forceProtocolDetection,
65623
- }), abortSignal);
65624
- }
65625
- catch (err) {
65626
- if (err.errorCode === hdShared.HardwareErrorCode.BleTimeoutError &&
65627
- device.mainId &&
65628
- device.deviceConnector) {
65629
- yield device.deviceConnector.disconnect(device.mainId).catch(() => undefined);
65630
- device.markTransportDisconnected();
65631
- }
65632
- throw err;
65633
- }
65634
- }
65577
+ yield device.acquire(method.payload.connectProtocol, {
65578
+ forceProtocolDetection: method.payload.forceProtocolDetection,
65579
+ });
65635
65580
  }
65636
65581
  if ((_a = method.payload) === null || _a === void 0 ? void 0 : _a.onlyConnectBleDevice) {
65637
65582
  if (shouldAcquire) {
@@ -65662,7 +65607,7 @@ function connectDeviceForBle(method, device, abortSignal, retryCount = 0) {
65662
65607
  const nextRetry = retryCount + 1;
65663
65608
  Log.debug(`Bluetooth connection will retry, retry count: ${nextRetry}`);
65664
65609
  yield wait(3000);
65665
- yield connectDeviceForBle(method, device, abortSignal, nextRetry);
65610
+ yield connectDeviceForBle(method, device, nextRetry);
65666
65611
  }
65667
65612
  else {
65668
65613
  throw err;
@@ -65747,7 +65692,7 @@ const ensureConnected = (_context, method, connectId, pollingId, abortSignal) =>
65747
65692
  if (tryCount === 1) {
65748
65693
  device.beginConnectionAttempt();
65749
65694
  }
65750
- yield connectDeviceForBle(method, device, abortSignal);
65695
+ yield connectDeviceForBle(method, device);
65751
65696
  }
65752
65697
  resolve(device);
65753
65698
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-core",
3
- "version": "1.2.0-alpha.174",
3
+ "version": "1.2.0-alpha.176",
4
4
  "description": "Core processes and APIs for communicating with OneKey hardware devices.",
5
5
  "author": "OneKey",
6
6
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
@@ -25,8 +25,8 @@
25
25
  "url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
26
26
  },
27
27
  "dependencies": {
28
- "@onekeyfe/hd-shared": "1.2.0-alpha.174",
29
- "@onekeyfe/hd-transport": "1.2.0-alpha.174",
28
+ "@onekeyfe/hd-shared": "1.2.0-alpha.176",
29
+ "@onekeyfe/hd-transport": "1.2.0-alpha.176",
30
30
  "axios": "1.15.2",
31
31
  "bignumber.js": "^9.0.2",
32
32
  "buffer": "^6.0.3",
@@ -46,5 +46,5 @@
46
46
  "@types/w3c-web-usb": "^1.0.10",
47
47
  "@types/web-bluetooth": "^0.0.21"
48
48
  },
49
- "gitHead": "ea906ff2ef191338cbc98d083e8217ddfb23c92c"
49
+ "gitHead": "a5152148317effe811cde8adecdfe75c28e82657"
50
50
  }
@@ -530,9 +530,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
530
530
 
531
531
  private protocolV2InstallBaselineVersions = new Map<number, string>();
532
532
 
533
- private protocolV2InstallNeedsBleReconnect = false;
533
+ private protocolV2InstallNeedsReconnect = false;
534
534
 
535
- private protocolV2InstallRequestConfirmed = false;
535
+ private protocolV2InstallTerminalSuccessObserved = false;
536
536
 
537
537
  private protocolV2LastRuntimeProbeFeatures?: Features;
538
538
 
@@ -2441,13 +2441,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2441
2441
  const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
2442
2442
  const startTime = Date.now();
2443
2443
  let lastError: unknown;
2444
- // USB may keep the loader link usable. BLE can release it as installation starts, so its
2445
- // recovery reconnects directly to status polling without generic Ping or DeviceInfo probes.
2446
- let shouldReconnect = this.protocolV2InstallNeedsBleReconnect;
2447
- this.protocolV2InstallNeedsBleReconnect = false;
2444
+ // The loader may release either USB or BLE as installation starts. Recovery reconnects
2445
+ // directly to status polling without generic Ping or DeviceInfo probes.
2446
+ let shouldReconnect = this.protocolV2InstallNeedsReconnect;
2447
+ this.protocolV2InstallNeedsReconnect = false;
2448
2448
  let deviceInfo: ProtocolV2DeviceInfo | undefined;
2449
2449
  let bleInstallLinkReady = false;
2450
- let installEvidenceObserved = this.protocolV2InstallRequestConfirmed;
2450
+ let installEvidenceObserved = this.protocolV2InstallTerminalSuccessObserved;
2451
2451
  let currentInstallStatusObserved = false;
2452
2452
  const liveTargetIds = new Set<number>();
2453
2453
 
@@ -2465,7 +2465,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2465
2465
  : await this.verifyProtocolV2ReconnectIdentity();
2466
2466
  shouldReconnect = false;
2467
2467
  }
2468
- const currentDeviceInfo = deviceInfo;
2468
+ let currentDeviceInfo = deviceInfo;
2469
2469
  try {
2470
2470
  const statusResponse = await this.device.getCommands().typedCall(
2471
2471
  'DeviceFirmwareUpdateStatusGet',
@@ -2479,7 +2479,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2479
2479
  },
2480
2480
  { timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
2481
2481
  );
2482
- if (this.protocolV2InstallRequestConfirmed) {
2482
+ if (this.protocolV2InstallTerminalSuccessObserved) {
2483
2483
  installEvidenceObserved = true;
2484
2484
  }
2485
2485
  const statusTargets = (statusResponse.message.records ??
@@ -2502,23 +2502,33 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2502
2502
  const targetId = normalizeProtocolV2TargetId(target.target_id);
2503
2503
  return targetId !== undefined && expectedTargetIds.has(targetId);
2504
2504
  });
2505
+ const hasCurrentInstallEvidence =
2506
+ currentInstallStatusObserved || this.protocolV2InstallTerminalSuccessObserved;
2505
2507
  const shouldVerifyTargetCompletion =
2506
- !requireCurrentInstallStatus || currentInstallStatusObserved;
2508
+ !requireCurrentInstallStatus || hasCurrentInstallEvidence;
2507
2509
  if (
2508
2510
  shouldVerifyTargetCompletion &&
2509
2511
  this.assertProtocolV2TargetStatus(
2510
2512
  statusTargets,
2511
2513
  expectedTargetIds,
2512
2514
  expectedPaths,
2513
- requireCurrentInstallStatus ? liveTargetIds : undefined
2515
+ requireCurrentInstallStatus && !this.protocolV2InstallTerminalSuccessObserved
2516
+ ? liveTargetIds
2517
+ : undefined
2514
2518
  )
2515
2519
  ) {
2516
2520
  return;
2517
2521
  }
2518
2522
 
2523
+ if (!hasCurrentInstallEvidence) {
2524
+ // A successful status poll clears DeviceCommands' cancel action. Keep cancellation
2525
+ // available while UpdateRequest is still waiting for confirmation on the device.
2526
+ this.device.setCancelableAction(() => this.device.getCommands().cancelDevice());
2527
+ }
2528
+
2519
2529
  if (
2520
2530
  requireCurrentInstallStatus &&
2521
- !currentInstallStatusObserved &&
2531
+ !hasCurrentInstallEvidence &&
2522
2532
  matchingStatusTargets.length > 0
2523
2533
  ) {
2524
2534
  lastError = new Error(
@@ -2526,6 +2536,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2526
2536
  );
2527
2537
  }
2528
2538
 
2539
+ if (
2540
+ statusTargets.length === 0 &&
2541
+ !currentDeviceInfo &&
2542
+ bleInstallLinkReady &&
2543
+ installEvidenceObserved
2544
+ ) {
2545
+ // BLE install reconnect skips generic probes because loaders may not answer Ping.
2546
+ // Restore the verified identity only after current-install evidence disappears.
2547
+ currentDeviceInfo = await this.verifyProtocolV2ReconnectIdentity();
2548
+ deviceInfo = currentDeviceInfo;
2549
+ }
2550
+
2529
2551
  if (statusTargets.length === 0 && currentDeviceInfo) {
2530
2552
  const isNormalMode = await this.probeProtocolV2NormalMode(currentDeviceInfo);
2531
2553
  if (
@@ -2960,11 +2982,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2960
2982
  targets: Array<{ target_id: number; path: string }>;
2961
2983
  }) {
2962
2984
  this.protocolV2LastRuntimeProbeFeatures = undefined;
2963
- this.protocolV2InstallNeedsBleReconnect = false;
2964
- this.protocolV2InstallRequestConfirmed = false;
2985
+ this.protocolV2InstallNeedsReconnect = false;
2986
+ this.protocolV2InstallTerminalSuccessObserved = false;
2965
2987
  const commands = this.device.getCommands();
2966
2988
  await commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
2967
- this.device.setCancelableAction(() => commands.cancelDevice());
2989
+ this.device.setCancelableAction(() => this.device.getCommands().cancelDevice());
2968
2990
  const interaction = this.device.createProtocolV2UiPhaseMetadata('button', 'start');
2969
2991
  this.postMessage(
2970
2992
  createUiMessage(UI_REQUEST.REQUEST_BUTTON, {
@@ -2978,55 +3000,31 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2978
3000
  })
2979
3001
  );
2980
3002
 
2981
- if (this.isBleReconnect()) {
2982
- try {
2983
- await commands.call(
2984
- 'DeviceFirmwareUpdateRequest',
2985
- {},
2986
- {
2987
- returnAfterWrite: true,
2988
- expectedTypes: ['Success'],
2989
- onResponseAfterWrite: response => {
2990
- if (response.type !== 'Success') return;
2991
- this.protocolV2InstallRequestConfirmed = true;
2992
- this.device.clearCancelableAction();
2993
- this.postProgressMessage(1, 'installingFirmware');
2994
- Log.log('[FirmwareUpdateV4] BLE firmware install confirmed by device response');
2995
- },
2996
- }
2997
- );
2998
- Log.log(
2999
- '[FirmwareUpdateV4] BLE firmware install request written; continue with status polling'
3000
- );
3001
- } catch (error) {
3002
- this.throwIfAborted();
3003
- if (!isProtocolV2DeviceDisconnectedError(error)) {
3004
- throw error;
3005
- }
3006
- Log.log(
3007
- '[FirmwareUpdateV4] BLE transport released while writing install request; continue status polling'
3008
- );
3009
- this.protocolV2InstallNeedsBleReconnect = true;
3010
- }
3011
- return;
3012
- }
3013
-
3014
3003
  try {
3015
- await commands.typedCall('DeviceFirmwareUpdateRequest', 'Success', {});
3016
- this.protocolV2InstallRequestConfirmed = true;
3017
- this.postProgressMessage(1, 'installingFirmware');
3018
- Log.log('[FirmwareUpdateV4] firmware install confirmed by device response');
3004
+ await commands.call(
3005
+ 'DeviceFirmwareUpdateRequest',
3006
+ {},
3007
+ {
3008
+ returnAfterWrite: true,
3009
+ expectedTypes: ['Success'],
3010
+ onResponseAfterWrite: response => {
3011
+ if (response.type !== 'Success') return;
3012
+ this.protocolV2InstallTerminalSuccessObserved = true;
3013
+ this.device.clearCancelableAction();
3014
+ Log.log('[FirmwareUpdateV4] firmware install completed by device response');
3015
+ },
3016
+ }
3017
+ );
3018
+ Log.log('[FirmwareUpdateV4] firmware install request written; continue with status polling');
3019
3019
  } catch (error) {
3020
3020
  this.throwIfAborted();
3021
3021
  if (!isProtocolV2DeviceDisconnectedError(error)) {
3022
3022
  throw error;
3023
3023
  }
3024
3024
  Log.log(
3025
- '[FirmwareUpdateV4] BLE transport released after install request; continue status polling'
3025
+ '[FirmwareUpdateV4] transport released while writing install request; continue status polling'
3026
3026
  );
3027
- if (this.isBleReconnect()) {
3028
- this.protocolV2InstallNeedsBleReconnect = true;
3029
- }
3027
+ this.protocolV2InstallNeedsReconnect = true;
3030
3028
  }
3031
3029
  }
3032
3030
 
package/src/core/index.ts CHANGED
@@ -415,13 +415,6 @@ const onCallDevice = async (
415
415
  if (method.payload?.onlyConnectBleDevice) {
416
416
  preWarmCallbackTask?.resolve();
417
417
  Log.debug('Call API - only connect ble device: ', device?.mainId);
418
- // This early return bypasses the normal-path bookkeeping at the end of the
419
- // call. Without it the task leaks and haunts every later queue snapshot
420
- // and cancel sweep (field log: a completed task lingered for 6 minutes),
421
- // and the request stays in the active maps, so repeated preconnects pile
422
- // up phantom work in diagnostics.
423
- completeMethodRequestContext(method);
424
- requestQueue.releaseTask(method.responseID);
425
418
  return createResponseMessage(method.responseID, true, null);
426
419
  }
427
420
 
@@ -961,60 +954,7 @@ export function isMissingDetectedProtocolV2Error(method: BaseMethod, error: unkn
961
954
  * If the Bluetooth connection times out, retry up to 6 times
962
955
  * @param retryCount - Current retry count (default 0)
963
956
  */
964
- // device.acquire awaits a transport reply with no deadline of its own; a
965
- // transport that never settles (field case: Electron main lost an IPC reply,
966
- // "reply was never sent" after 5 minutes) hangs the call forever and cancel()
967
- // only takes effect at poll checkpoints. Race acquire against a deadline and
968
- // the caller's abort signal so the hang is bounded and cancel is immediate.
969
- const BLE_ACQUIRE_DEADLINE_MS = 60 * 1000;
970
-
971
- function raceBleAcquire<T>(acquirePromise: Promise<T>, abortSignal?: AbortSignal): Promise<T> {
972
- return new Promise<T>((resolve, reject) => {
973
- let settled = false;
974
- const settle = (fn: () => void) => {
975
- if (settled) return;
976
- settled = true;
977
- clearTimeout(deadline);
978
- abortSignal?.removeEventListener('abort', onAbort);
979
- fn();
980
- };
981
- const onAbort = () =>
982
- settle(() => reject(ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled)));
983
- const deadline = setTimeout(
984
- () =>
985
- settle(() =>
986
- reject(
987
- ERRORS.TypedError(
988
- HardwareErrorCode.BleTimeoutError,
989
- `BLE acquire exceeded ${BLE_ACQUIRE_DEADLINE_MS}ms deadline`
990
- )
991
- )
992
- ),
993
- BLE_ACQUIRE_DEADLINE_MS
994
- );
995
- // Attach before any early return so a late settlement of acquirePromise
996
- // is always consumed — an abort or deadline must never leave the acquire
997
- // rejection unhandled.
998
- acquirePromise.then(
999
- value => settle(() => resolve(value)),
1000
- error => settle(() => reject(error))
1001
- );
1002
- if (abortSignal) {
1003
- if (abortSignal.aborted) {
1004
- onAbort();
1005
- return;
1006
- }
1007
- abortSignal.addEventListener('abort', onAbort);
1008
- }
1009
- });
1010
- }
1011
-
1012
- async function connectDeviceForBle(
1013
- method: BaseMethod,
1014
- device: Device,
1015
- abortSignal?: AbortSignal,
1016
- retryCount = 0
1017
- ) {
957
+ async function connectDeviceForBle(method: BaseMethod, device: Device, retryCount = 0) {
1018
958
  try {
1019
959
  if (device.wasInterruptedByUser()) {
1020
960
  throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
@@ -1028,43 +968,9 @@ async function connectDeviceForBle(
1028
968
  !device.commands ||
1029
969
  device.commands.disposed;
1030
970
  if (shouldAcquire) {
1031
- // The deadline/abort guards are scoped to the desktop electron
1032
- // transport: its IPC acquire is the only path with a proven
1033
- // never-settling failure mode, while react-native/lowlevel acquire may
1034
- // legitimately block on a user-driven system bonding prompt for longer
1035
- // than any sane deadline. Other envs keep the plain acquire unchanged.
1036
- const useAcquireGuards = DataManager.getSettings('env') === 'desktop-web-ble';
1037
- // A cancel landing during the retry backoff must not start a new acquire.
1038
- if (useAcquireGuards && abortSignal?.aborted) {
1039
- throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
1040
- }
1041
- if (!useAcquireGuards) {
1042
- await device.acquire(method.payload.connectProtocol, {
1043
- forceProtocolDetection: method.payload.forceProtocolDetection,
1044
- });
1045
- } else {
1046
- try {
1047
- await raceBleAcquire(
1048
- device.acquire(method.payload.connectProtocol, {
1049
- forceProtocolDetection: method.payload.forceProtocolDetection,
1050
- }),
1051
- abortSignal
1052
- );
1053
- } catch (err) {
1054
- // A deadline hit means the transport is wedged mid-acquire; drop the
1055
- // link before the retry so it cold-connects instead of stacking a
1056
- // second connect onto the half-open one.
1057
- if (
1058
- err.errorCode === HardwareErrorCode.BleTimeoutError &&
1059
- device.mainId &&
1060
- device.deviceConnector
1061
- ) {
1062
- await device.deviceConnector.disconnect(device.mainId).catch(() => undefined);
1063
- device.markTransportDisconnected();
1064
- }
1065
- throw err;
1066
- }
1067
- }
971
+ await device.acquire(method.payload.connectProtocol, {
972
+ forceProtocolDetection: method.payload.forceProtocolDetection,
973
+ });
1068
974
  }
1069
975
  if (method.payload?.onlyConnectBleDevice) {
1070
976
  if (shouldAcquire) {
@@ -1104,7 +1010,7 @@ async function connectDeviceForBle(
1104
1010
  const nextRetry = retryCount + 1;
1105
1011
  Log.debug(`Bluetooth connection will retry, retry count: ${nextRetry}`);
1106
1012
  await wait(3000);
1107
- await connectDeviceForBle(method, device, abortSignal, nextRetry);
1013
+ await connectDeviceForBle(method, device, nextRetry);
1108
1014
  } else {
1109
1015
  throw err;
1110
1016
  }
@@ -1214,7 +1120,7 @@ const ensureConnected = async (
1214
1120
  if (tryCount === 1) {
1215
1121
  device.beginConnectionAttempt();
1216
1122
  }
1217
- await connectDeviceForBle(method, device, abortSignal);
1123
+ await connectDeviceForBle(method, device);
1218
1124
  }
1219
1125
  resolve(device);
1220
1126
  return;