@onekeyfe/hd-core 1.2.2-alpha.0 → 1.2.2-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +2 -2
- package/__tests__/device-lifecycle-events.test.ts +67 -0
- package/__tests__/deviceUploadNft.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +322 -21
- package/__tests__/open-wallet-session.test.ts +613 -80
- package/__tests__/pro2HostAssetPackage.test.ts +108 -1
- package/__tests__/protocol-v2.test.ts +231 -49
- package/__tests__/protocolV2FileWrite.test.ts +40 -0
- package/dist/api/FirmwareUpdateV4.d.ts +2 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +1 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/core/RequestQueue.d.ts +2 -0
- package/dist/core/RequestQueue.d.ts.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +1454 -1144
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +73 -23
- package/src/api/OpenWalletSession.ts +0 -3
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +3 -1
- package/src/api/helpers/protocolV2FileWrite.ts +11 -5
- package/src/api/protocol-v2/DeviceUploadNft.ts +4 -1
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +17 -3
- package/src/core/RequestQueue.ts +16 -1
- package/src/core/index.ts +48 -21
- package/src/data/messages/messages-protocol-v2.json +1471 -1367
- package/src/protocols/protocol-v2/walletSession.ts +157 -38
- package/src/utils/pro2HostAssetPackage.ts +143 -58
|
@@ -95,7 +95,7 @@ describe('AllNetworkGetAddressBase tracing', () => {
|
|
|
95
95
|
'hidden-state',
|
|
96
96
|
false,
|
|
97
97
|
undefined,
|
|
98
|
-
|
|
98
|
+
undefined,
|
|
99
99
|
undefined
|
|
100
100
|
);
|
|
101
101
|
expect(calls).toEqual(['resume-hidden-session', 'run-chain-method']);
|
|
@@ -264,7 +264,7 @@ describe('AllNetworkGetAddressBase tracing', () => {
|
|
|
264
264
|
undefined,
|
|
265
265
|
true,
|
|
266
266
|
undefined,
|
|
267
|
-
|
|
267
|
+
undefined,
|
|
268
268
|
true
|
|
269
269
|
);
|
|
270
270
|
expect(calls).toEqual(['resume-standard-session', 'run-chain-method']);
|
|
@@ -2,12 +2,14 @@ import { EDeviceType, ERRORS, HardwareErrorCode, createDeferred } from '@onekeyf
|
|
|
2
2
|
import { DeviceType, TRANSPORT_EVENT } from '@onekeyfe/hd-transport';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
+
cancel,
|
|
5
6
|
initConnector,
|
|
6
7
|
initCore,
|
|
7
8
|
isMissingDetectedProtocolV2Error,
|
|
8
9
|
isProtocolV2PeerRemovedPairingError,
|
|
9
10
|
isRetryableBleConnectionError,
|
|
10
11
|
isRetryableBleProtocolV2ProbeError,
|
|
12
|
+
resolveBleConnectProtocol,
|
|
11
13
|
} from '../src/core';
|
|
12
14
|
import { DataManager } from '../src/data-manager';
|
|
13
15
|
import TransportManager from '../src/data-manager/TransportManager';
|
|
@@ -71,6 +73,19 @@ describe('public device lifecycle events', () => {
|
|
|
71
73
|
jest.restoreAllMocks();
|
|
72
74
|
});
|
|
73
75
|
|
|
76
|
+
test('prefers Protocol V2 only when the method contract is explicitly V2-only', () => {
|
|
77
|
+
const createMethod = (protocols: readonly ('V1' | 'V2')[], connectProtocol?: 'V1' | 'V2') =>
|
|
78
|
+
({
|
|
79
|
+
payload: { connectProtocol },
|
|
80
|
+
getSupportedProtocols: () => protocols,
|
|
81
|
+
} as never);
|
|
82
|
+
|
|
83
|
+
expect(resolveBleConnectProtocol(createMethod(['V2']))).toBe('V2');
|
|
84
|
+
expect(resolveBleConnectProtocol(createMethod(['V1']))).toBeUndefined();
|
|
85
|
+
expect(resolveBleConnectProtocol(createMethod(['V1', 'V2']))).toBeUndefined();
|
|
86
|
+
expect(resolveBleConnectProtocol(createMethod(['V2'], 'V1'))).toBe('V1');
|
|
87
|
+
});
|
|
88
|
+
|
|
74
89
|
test('registers the shared device lifecycle listeners exactly once', async () => {
|
|
75
90
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
76
91
|
core = initCore();
|
|
@@ -106,6 +121,58 @@ describe('public device lifecycle events', () => {
|
|
|
106
121
|
expect(context.getPrePendingCallPromise('device-a')).toBe(replacementCleanup.promise);
|
|
107
122
|
});
|
|
108
123
|
|
|
124
|
+
test('cancels only requests associated with the requested connect id', () => {
|
|
125
|
+
core = initCore();
|
|
126
|
+
const context = (core as any).getCoreContext();
|
|
127
|
+
const deviceA = {
|
|
128
|
+
mainId: 'transport-session-a',
|
|
129
|
+
getConnectId: jest.fn(() => 'serial-a'),
|
|
130
|
+
interruptionFromUser: jest.fn().mockResolvedValue(undefined),
|
|
131
|
+
};
|
|
132
|
+
const deviceB = {
|
|
133
|
+
mainId: 'device-b',
|
|
134
|
+
getConnectId: jest.fn(() => 'serial-b'),
|
|
135
|
+
interruptionFromUser: jest.fn().mockResolvedValue(undefined),
|
|
136
|
+
};
|
|
137
|
+
const taskA = context.requestQueue.createTask({
|
|
138
|
+
responseID: 101,
|
|
139
|
+
connectId: '',
|
|
140
|
+
device: deviceA,
|
|
141
|
+
} as never);
|
|
142
|
+
const taskB = context.requestQueue.createTask({
|
|
143
|
+
responseID: 102,
|
|
144
|
+
connectId: 'device-b',
|
|
145
|
+
device: deviceB,
|
|
146
|
+
} as never);
|
|
147
|
+
const signalA = taskA.abortController?.signal;
|
|
148
|
+
const signalB = taskB.abortController?.signal;
|
|
149
|
+
|
|
150
|
+
cancel(context, 'serial-a');
|
|
151
|
+
|
|
152
|
+
expect(signalA?.aborted).toBe(true);
|
|
153
|
+
expect(context.requestQueue.getTask(taskA.id)).toBeUndefined();
|
|
154
|
+
expect(signalB?.aborted).toBe(false);
|
|
155
|
+
expect(context.requestQueue.getTask(taskB.id)).toBe(taskB);
|
|
156
|
+
expect(deviceA.interruptionFromUser).toHaveBeenCalledTimes(1);
|
|
157
|
+
expect(deviceB.interruptionFromUser).not.toHaveBeenCalled();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('aborts every request before cancel-all rejects WebUSB tasks', () => {
|
|
161
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('webusb' as never);
|
|
162
|
+
core = initCore();
|
|
163
|
+
const context = (core as any).getCoreContext();
|
|
164
|
+
const task = context.requestQueue.createTask({
|
|
165
|
+
responseID: 103,
|
|
166
|
+
connectId: 'webusb-device',
|
|
167
|
+
} as never);
|
|
168
|
+
const signal = task.abortController?.signal;
|
|
169
|
+
|
|
170
|
+
cancel(context);
|
|
171
|
+
|
|
172
|
+
expect(signal?.aborted).toBe(true);
|
|
173
|
+
expect(context.requestQueue.getTask(task.id)).toBeUndefined();
|
|
174
|
+
});
|
|
175
|
+
|
|
109
176
|
test('keeps shared device lifecycle listeners across a device cache reset', () => {
|
|
110
177
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
111
178
|
core = initCore();
|
|
@@ -54,6 +54,7 @@ const createMethod = ({
|
|
|
54
54
|
});
|
|
55
55
|
(method as any).device = {
|
|
56
56
|
commands: { typedCall },
|
|
57
|
+
state: { versions: { firmware: firmwareVersion } },
|
|
57
58
|
ensureProtocolV2RuntimeContext: jest.fn(() =>
|
|
58
59
|
Promise.resolve({
|
|
59
60
|
version: 2,
|
|
@@ -243,6 +244,32 @@ describe('DeviceUploadNft', () => {
|
|
|
243
244
|
});
|
|
244
245
|
});
|
|
245
246
|
|
|
247
|
+
test('keeps the legacy upload flow on a 1.0.1 prerelease', async () => {
|
|
248
|
+
const typedCall = jest.fn((request: string, _response: string, params: any) => {
|
|
249
|
+
if (request === 'FilesystemPathInfoQuery') {
|
|
250
|
+
return { message: { exist: true, directory: true } };
|
|
251
|
+
}
|
|
252
|
+
if (request === 'FilesystemDirList') {
|
|
253
|
+
return { message: { path: 'vol1:/nft', child_files: '' } };
|
|
254
|
+
}
|
|
255
|
+
if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
|
|
256
|
+
if (request === 'NftUpdate') return { message: {} };
|
|
257
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
258
|
+
});
|
|
259
|
+
const method = createMethod({ typedCall, firmwareVersion: '1.0.1-beta.1' });
|
|
260
|
+
|
|
261
|
+
await method.run();
|
|
262
|
+
|
|
263
|
+
const requests = typedCall.mock.calls.map(call => call[0]);
|
|
264
|
+
expect(requests).toContain('FilesystemPathInfoQuery');
|
|
265
|
+
expect(requests).toContain('FilesystemDirList');
|
|
266
|
+
const paths = typedCall.mock.calls
|
|
267
|
+
.filter(call => call[0] === 'FilesystemFileWrite')
|
|
268
|
+
.map(call => call[2].file.path as string);
|
|
269
|
+
expect(paths.some(path => path.endsWith('.okpkg'))).toBe(false);
|
|
270
|
+
expect(paths).toContain('vol1:/nft/nft-deadbeef-1760000000000.bin');
|
|
271
|
+
});
|
|
272
|
+
|
|
246
273
|
test('treats a missing NFT directory as empty before the first upload', async () => {
|
|
247
274
|
const typedCall = jest.fn((request: string, _response: string, params: any) => {
|
|
248
275
|
if (request === 'FilesystemPathInfoQuery') {
|
|
@@ -335,7 +335,97 @@ describe('FirmwareUpdateV4 install polling', () => {
|
|
|
335
335
|
expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
|
|
336
336
|
});
|
|
337
337
|
|
|
338
|
-
test(
|
|
338
|
+
test.each([
|
|
339
|
+
{
|
|
340
|
+
component: 'boot',
|
|
341
|
+
targets: [{ target_id: 3, path: 'vol0:/bootloader.bin' }],
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
component: 'P1',
|
|
345
|
+
targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
component: 'P2',
|
|
349
|
+
targets: [{ target_id: 5, path: 'vol0:/application_p2.bin' }],
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
component: 'coprocessor',
|
|
353
|
+
targets: [{ target_id: 6, path: 'vol0:/coprocessor.bin' }],
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
component: 'SE',
|
|
357
|
+
targets: [
|
|
358
|
+
{ target_id: 7, path: 'vol0:/se01.bin' },
|
|
359
|
+
{ target_id: 8, path: 'vol0:/se02.bin' },
|
|
360
|
+
{ target_id: 9, path: 'vol0:/se03.bin' },
|
|
361
|
+
{ target_id: 10, path: 'vol0:/se04.bin' },
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
])(
|
|
365
|
+
'accepts stable finished BLE status for $component after an actual install disconnect hides in-progress',
|
|
366
|
+
async ({ targets }) => {
|
|
367
|
+
const method = new FirmwareUpdateV4({
|
|
368
|
+
id: 1,
|
|
369
|
+
payload: {
|
|
370
|
+
method: 'firmwareUpdateV4',
|
|
371
|
+
connectId: 'pro2-ble',
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
const finishedStatus = {
|
|
375
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
376
|
+
message: {
|
|
377
|
+
records: targets.map(target => ({
|
|
378
|
+
...target,
|
|
379
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
380
|
+
})),
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
const typedCall = jest
|
|
384
|
+
.fn()
|
|
385
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
386
|
+
.mockImplementationOnce(() => {
|
|
387
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
388
|
+
throw new Error('device was disconnected');
|
|
389
|
+
})
|
|
390
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
391
|
+
.mockImplementationOnce(() => {
|
|
392
|
+
expect(method.postProgressMessage).toHaveBeenCalledTimes(1);
|
|
393
|
+
expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
|
|
394
|
+
return finishedStatus;
|
|
395
|
+
})
|
|
396
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
397
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
398
|
+
.mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
|
|
399
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
400
|
+
|
|
401
|
+
method.device = {
|
|
402
|
+
getCommands: () => ({ typedCall }),
|
|
403
|
+
setCancelableAction: jest.fn(),
|
|
404
|
+
} as unknown as Device;
|
|
405
|
+
method.postProgressMessage = jest.fn();
|
|
406
|
+
|
|
407
|
+
const firmwareUpdate = method as unknown as {
|
|
408
|
+
waitForProtocolV2FirmwareUpdateComplete: (
|
|
409
|
+
value: typeof targets,
|
|
410
|
+
requireCurrentInstallStatus: boolean
|
|
411
|
+
) => Promise<void>;
|
|
412
|
+
reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
|
|
413
|
+
};
|
|
414
|
+
firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
415
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
416
|
+
|
|
417
|
+
await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
418
|
+
|
|
419
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
|
|
420
|
+
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
421
|
+
expect(method.postProgressMessage).toHaveBeenNthCalledWith(1, 1, 'installingFirmware');
|
|
422
|
+
expect(method.postProgressMessage).toHaveBeenNthCalledWith(2, 100, 'installingFirmware');
|
|
423
|
+
expect(method.postProgressMessage).toHaveBeenCalledTimes(2);
|
|
424
|
+
},
|
|
425
|
+
10_000
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
test('uses a real BLE disconnect while writing the Request as install evidence', async () => {
|
|
339
429
|
const method = new FirmwareUpdateV4({
|
|
340
430
|
id: 1,
|
|
341
431
|
payload: {
|
|
@@ -343,49 +433,41 @@ describe('FirmwareUpdateV4 install polling', () => {
|
|
|
343
433
|
connectId: 'pro2-ble',
|
|
344
434
|
},
|
|
345
435
|
});
|
|
346
|
-
const targets = [{ target_id:
|
|
436
|
+
const targets = [{ target_id: 3, path: 'vol0:/bootloader.bin' }];
|
|
347
437
|
const finishedStatus = {
|
|
348
438
|
type: 'DeviceFirmwareUpdateStatus',
|
|
349
439
|
message: {
|
|
350
440
|
records: [
|
|
351
441
|
{
|
|
352
|
-
|
|
442
|
+
...targets[0],
|
|
353
443
|
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
354
|
-
payload_version: 65_556,
|
|
355
|
-
path: 'vol0:/coprocessor.bin',
|
|
356
444
|
},
|
|
357
445
|
],
|
|
358
446
|
},
|
|
359
447
|
};
|
|
360
448
|
const typedCall = jest
|
|
361
449
|
.fn()
|
|
450
|
+
.mockResolvedValueOnce({ type: 'Success', message: {} })
|
|
362
451
|
.mockResolvedValueOnce(finishedStatus)
|
|
363
|
-
.mockImplementationOnce(() => {
|
|
364
|
-
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
365
|
-
throw new Error('device was disconnected');
|
|
366
|
-
})
|
|
367
452
|
.mockResolvedValueOnce(finishedStatus)
|
|
368
|
-
.mockImplementationOnce(() => {
|
|
369
|
-
expect(method.postProgressMessage).toHaveBeenCalledTimes(1);
|
|
370
|
-
expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
|
|
371
|
-
return finishedStatus;
|
|
372
|
-
})
|
|
373
453
|
.mockResolvedValueOnce(finishedStatus)
|
|
374
454
|
.mockResolvedValueOnce(finishedStatus)
|
|
375
455
|
.mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
|
|
456
|
+
const call = jest.fn().mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError));
|
|
376
457
|
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
377
458
|
|
|
378
|
-
(method as any).params = {
|
|
379
|
-
expectedTargetVersions: { coprocessor: '1.0.20' },
|
|
380
|
-
};
|
|
381
|
-
|
|
382
459
|
method.device = {
|
|
383
|
-
getCommands: () => ({ typedCall }),
|
|
460
|
+
getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
|
|
461
|
+
createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
|
|
462
|
+
toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-ble' }),
|
|
384
463
|
setCancelableAction: jest.fn(),
|
|
464
|
+
clearCancelableAction: jest.fn(),
|
|
385
465
|
} as unknown as Device;
|
|
466
|
+
method.postMessage = jest.fn();
|
|
386
467
|
method.postProgressMessage = jest.fn();
|
|
387
468
|
|
|
388
469
|
const firmwareUpdate = method as unknown as {
|
|
470
|
+
protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
|
|
389
471
|
waitForProtocolV2FirmwareUpdateComplete: (
|
|
390
472
|
value: typeof targets,
|
|
391
473
|
requireCurrentInstallStatus: boolean
|
|
@@ -395,15 +477,234 @@ describe('FirmwareUpdateV4 install polling', () => {
|
|
|
395
477
|
firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
396
478
|
(method as any).isBleReconnect = jest.fn(() => true);
|
|
397
479
|
|
|
480
|
+
await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
|
|
398
481
|
await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
399
482
|
|
|
400
483
|
expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
|
|
401
|
-
expect(typedCall).toHaveBeenCalledTimes(6);
|
|
402
484
|
expect(method.postProgressMessage).toHaveBeenNthCalledWith(1, 1, 'installingFirmware');
|
|
403
485
|
expect(method.postProgressMessage).toHaveBeenNthCalledWith(2, 100, 'installingFirmware');
|
|
404
|
-
expect(
|
|
486
|
+
expect(typedCall).toHaveBeenCalledTimes(5);
|
|
487
|
+
}, 10_000);
|
|
488
|
+
|
|
489
|
+
test('does not use a Request response timeout as BLE install evidence', async () => {
|
|
490
|
+
const method = new FirmwareUpdateV4({
|
|
491
|
+
id: 1,
|
|
492
|
+
payload: {
|
|
493
|
+
method: 'firmwareUpdateV4',
|
|
494
|
+
connectId: 'pro2-ble',
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
const targets = [{ target_id: 3, path: 'vol0:/bootloader.bin' }];
|
|
498
|
+
const finishedStatus = {
|
|
499
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
500
|
+
message: {
|
|
501
|
+
records: [
|
|
502
|
+
{
|
|
503
|
+
...targets[0],
|
|
504
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
const typedCall = jest
|
|
510
|
+
.fn()
|
|
511
|
+
.mockResolvedValueOnce({ type: 'Success', message: {} })
|
|
512
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
513
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
514
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
515
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
516
|
+
.mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
|
|
517
|
+
const call = jest
|
|
518
|
+
.fn()
|
|
519
|
+
.mockRejectedValue(
|
|
520
|
+
ERRORS.TypedError(
|
|
521
|
+
HardwareErrorCode.BleTimeoutError,
|
|
522
|
+
'device was disconnected after Lowlevel response timeout'
|
|
523
|
+
)
|
|
524
|
+
);
|
|
525
|
+
const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
|
|
526
|
+
|
|
527
|
+
method.device = {
|
|
528
|
+
getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
|
|
529
|
+
createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
|
|
530
|
+
toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-ble' }),
|
|
531
|
+
setCancelableAction: jest.fn(),
|
|
532
|
+
clearCancelableAction: jest.fn(),
|
|
533
|
+
} as unknown as Device;
|
|
534
|
+
method.postMessage = jest.fn();
|
|
535
|
+
method.postProgressMessage = jest.fn();
|
|
536
|
+
|
|
537
|
+
const firmwareUpdate = method as unknown as {
|
|
538
|
+
protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
|
|
539
|
+
waitForProtocolV2FirmwareUpdateComplete: (
|
|
540
|
+
value: typeof targets,
|
|
541
|
+
requireCurrentInstallStatus: boolean
|
|
542
|
+
) => Promise<void>;
|
|
543
|
+
reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
|
|
544
|
+
};
|
|
545
|
+
firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
546
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
547
|
+
|
|
548
|
+
await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
|
|
549
|
+
await expect(
|
|
550
|
+
firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true)
|
|
551
|
+
).rejects.toMatchObject({
|
|
552
|
+
errorCode: HardwareErrorCode.ActionCancelled,
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
|
|
556
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
405
557
|
}, 10_000);
|
|
406
558
|
|
|
559
|
+
test('records a real BLE disconnect that occurs during install reconnect', async () => {
|
|
560
|
+
const method = new FirmwareUpdateV4({
|
|
561
|
+
id: 1,
|
|
562
|
+
payload: {
|
|
563
|
+
method: 'firmwareUpdateV4',
|
|
564
|
+
connectId: 'pro2-ble',
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
const targets = [{ target_id: 3, path: 'vol0:/bootloader.bin' }];
|
|
568
|
+
const finishedStatus = {
|
|
569
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
570
|
+
message: {
|
|
571
|
+
records: [
|
|
572
|
+
{
|
|
573
|
+
...targets[0],
|
|
574
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
575
|
+
},
|
|
576
|
+
],
|
|
577
|
+
},
|
|
578
|
+
};
|
|
579
|
+
const typedCall = jest
|
|
580
|
+
.fn()
|
|
581
|
+
.mockRejectedValueOnce(
|
|
582
|
+
ERRORS.TypedError(
|
|
583
|
+
HardwareErrorCode.BleTimeoutError,
|
|
584
|
+
'Lowlevel response timeout after 15000ms for DeviceFirmwareUpdateStatusGet'
|
|
585
|
+
)
|
|
586
|
+
)
|
|
587
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
588
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
589
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
590
|
+
.mockResolvedValueOnce(finishedStatus);
|
|
591
|
+
const reconnectProtocolV2Device = jest
|
|
592
|
+
.fn()
|
|
593
|
+
.mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError))
|
|
594
|
+
.mockResolvedValueOnce(undefined);
|
|
595
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
596
|
+
callback: () => void
|
|
597
|
+
) => {
|
|
598
|
+
callback();
|
|
599
|
+
return 0 as any;
|
|
600
|
+
}) as typeof setTimeout);
|
|
601
|
+
|
|
602
|
+
method.device = {
|
|
603
|
+
getCommands: () => ({ typedCall, cancelDevice: jest.fn() }),
|
|
604
|
+
setCancelableAction: jest.fn(),
|
|
605
|
+
} as unknown as Device;
|
|
606
|
+
method.postProgressMessage = jest.fn();
|
|
607
|
+
|
|
608
|
+
const firmwareUpdate = method as unknown as {
|
|
609
|
+
waitForProtocolV2FirmwareUpdateComplete: (
|
|
610
|
+
value: typeof targets,
|
|
611
|
+
requireCurrentInstallStatus: boolean
|
|
612
|
+
) => Promise<void>;
|
|
613
|
+
reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
|
|
614
|
+
};
|
|
615
|
+
firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
616
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
617
|
+
|
|
618
|
+
try {
|
|
619
|
+
await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
|
|
620
|
+
} finally {
|
|
621
|
+
setTimeoutSpy.mockRestore();
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
|
|
625
|
+
expect(method.postProgressMessage).toHaveBeenNthCalledWith(1, 1, 'installingFirmware');
|
|
626
|
+
expect(method.postProgressMessage).toHaveBeenNthCalledWith(2, 100, 'installingFirmware');
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
test('does not use a reconnect response timeout as BLE install evidence', async () => {
|
|
630
|
+
const method = new FirmwareUpdateV4({
|
|
631
|
+
id: 1,
|
|
632
|
+
payload: {
|
|
633
|
+
method: 'firmwareUpdateV4',
|
|
634
|
+
connectId: 'pro2-ble',
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
const targets = [{ target_id: 3, path: 'vol0:/bootloader.bin' }];
|
|
638
|
+
const finishedStatus = {
|
|
639
|
+
type: 'DeviceFirmwareUpdateStatus',
|
|
640
|
+
message: {
|
|
641
|
+
records: [
|
|
642
|
+
{
|
|
643
|
+
...targets[0],
|
|
644
|
+
status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
|
|
645
|
+
},
|
|
646
|
+
],
|
|
647
|
+
},
|
|
648
|
+
};
|
|
649
|
+
const typedCall = jest
|
|
650
|
+
.fn()
|
|
651
|
+
.mockRejectedValueOnce(
|
|
652
|
+
ERRORS.TypedError(
|
|
653
|
+
HardwareErrorCode.BleTimeoutError,
|
|
654
|
+
'Lowlevel response timeout after 15000ms for DeviceFirmwareUpdateStatusGet'
|
|
655
|
+
)
|
|
656
|
+
)
|
|
657
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
658
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
659
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
660
|
+
.mockResolvedValueOnce(finishedStatus)
|
|
661
|
+
.mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
|
|
662
|
+
const reconnectProtocolV2Device = jest
|
|
663
|
+
.fn()
|
|
664
|
+
.mockRejectedValueOnce(
|
|
665
|
+
ERRORS.TypedError(
|
|
666
|
+
HardwareErrorCode.BleTimeoutError,
|
|
667
|
+
'device was disconnected after Lowlevel response timeout'
|
|
668
|
+
)
|
|
669
|
+
)
|
|
670
|
+
.mockResolvedValueOnce(undefined);
|
|
671
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
|
|
672
|
+
callback: () => void
|
|
673
|
+
) => {
|
|
674
|
+
callback();
|
|
675
|
+
return 0 as any;
|
|
676
|
+
}) as typeof setTimeout);
|
|
677
|
+
|
|
678
|
+
method.device = {
|
|
679
|
+
getCommands: () => ({ typedCall, cancelDevice: jest.fn() }),
|
|
680
|
+
setCancelableAction: jest.fn(),
|
|
681
|
+
} as unknown as Device;
|
|
682
|
+
method.postProgressMessage = jest.fn();
|
|
683
|
+
|
|
684
|
+
const firmwareUpdate = method as unknown as {
|
|
685
|
+
waitForProtocolV2FirmwareUpdateComplete: (
|
|
686
|
+
value: typeof targets,
|
|
687
|
+
requireCurrentInstallStatus: boolean
|
|
688
|
+
) => Promise<void>;
|
|
689
|
+
reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
|
|
690
|
+
};
|
|
691
|
+
firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
|
|
692
|
+
(method as any).isBleReconnect = jest.fn(() => true);
|
|
693
|
+
|
|
694
|
+
try {
|
|
695
|
+
await expect(
|
|
696
|
+
firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true)
|
|
697
|
+
).rejects.toMatchObject({
|
|
698
|
+
errorCode: HardwareErrorCode.ActionCancelled,
|
|
699
|
+
});
|
|
700
|
+
} finally {
|
|
701
|
+
setTimeoutSpy.mockRestore();
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
|
|
705
|
+
expect(method.postProgressMessage).not.toHaveBeenCalled();
|
|
706
|
+
});
|
|
707
|
+
|
|
407
708
|
test('rejects stable stale finished BLE status after a status response timeout', async () => {
|
|
408
709
|
const method = new FirmwareUpdateV4({
|
|
409
710
|
id: 1,
|