@onekeyfe/hd-core 1.2.2-alpha.100 → 1.2.2-alpha.3
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 +20 -4
- package/__tests__/DeviceCommands.test.ts +13 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/device-lifecycle-events.test.ts +19 -0
- package/__tests__/deviceUploadNft.test.ts +68 -0
- package/__tests__/evmSignTypedData.test.ts +42 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +825 -12
- package/__tests__/open-wallet-session.test.ts +144 -37
- package/__tests__/pro2HostAssetPackage.test.ts +58 -0
- package/__tests__/protocol-v2-resources.test.ts +7 -4
- package/__tests__/protocol-v2-unlock-policy.test.ts +35 -1
- package/__tests__/protocol-v2.test.ts +720 -51
- package/dist/api/FirmwareUpdateV4.d.ts +4 -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/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +2 -2
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.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/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +1 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +1 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +3 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +21 -5
- package/dist/index.js +2288 -1439
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +2 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/settings.d.ts +2 -2
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
- package/dist/utils/deviceFeaturesUtils.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 +8 -0
- package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +434 -75
- package/src/api/OpenWalletSession.ts +39 -9
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +5 -2
- package/src/api/evm/EVMSignTypedData.ts +1 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
- package/src/api/protocol-v2/DeviceUploadNft.ts +31 -8
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +27 -8
- package/src/api/protocol-v2/helpers.ts +8 -0
- package/src/core/index.ts +130 -14
- package/src/data/messages/messages-protocol-v2.json +1541 -1332
- package/src/device/Device.ts +3 -1
- package/src/device/DeviceConnector.ts +7 -3
- package/src/events/ui-request.ts +3 -0
- package/src/protocols/protocol-v2/resources.ts +9 -1
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +8 -1
- package/src/protocols/protocol-v2/walletSession.ts +88 -50
- package/src/types/settings.ts +4 -2
- package/src/utils/deviceFeaturesUtils.ts +4 -0
- package/src/utils/pro2HostAssetPackage.ts +354 -0
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
EDeviceType,
|
|
3
|
+
ERRORS,
|
|
4
|
+
HardwareError,
|
|
5
|
+
HardwareErrorCode,
|
|
6
|
+
isBleStaleBondHardwareError,
|
|
7
|
+
wait,
|
|
8
|
+
} from '@onekeyfe/hd-shared';
|
|
2
9
|
import JSZip from 'jszip';
|
|
3
10
|
import {
|
|
4
11
|
DeviceRebootType,
|
|
@@ -44,6 +51,7 @@ import {
|
|
|
44
51
|
getProtocolV2UnknownErrorText,
|
|
45
52
|
isProtocolV2DeviceDisconnectedError,
|
|
46
53
|
} from './protocol-v2/helpers';
|
|
54
|
+
import { isProtocolV2ResponseTimeout } from './helpers/protocolV2FileWrite';
|
|
47
55
|
import {
|
|
48
56
|
openFirmwareByteSource,
|
|
49
57
|
readFirmwareByteSourceFully,
|
|
@@ -55,6 +63,7 @@ import {
|
|
|
55
63
|
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
56
64
|
validateFirmwareUpdatePreparedPlan,
|
|
57
65
|
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
66
|
+
|
|
58
67
|
import type {
|
|
59
68
|
FirmwareArtifactReference,
|
|
60
69
|
FirmwareUpdateV4Params,
|
|
@@ -84,6 +93,7 @@ const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
|
84
93
|
const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
|
|
85
94
|
const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
|
|
86
95
|
const PROTOCOL_V2_INSTALL_STATUS_INITIAL_DELAY = 1000;
|
|
96
|
+
const PROTOCOL_V2_INSTALL_FINISHED_AFTER_DISCONNECT_POLLS = 4;
|
|
87
97
|
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
88
98
|
const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
|
|
89
99
|
const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
|
|
@@ -182,6 +192,11 @@ const getProtocolV2DeviceTransferProgress = (
|
|
|
182
192
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
183
193
|
target_id: number | string;
|
|
184
194
|
status?: number | string;
|
|
195
|
+
progress_percent?: number;
|
|
196
|
+
phase_info?: {
|
|
197
|
+
phase: number | string;
|
|
198
|
+
progress_percent: number;
|
|
199
|
+
};
|
|
185
200
|
payload_version?: number;
|
|
186
201
|
path?: string;
|
|
187
202
|
};
|
|
@@ -359,12 +374,22 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
359
374
|
);
|
|
360
375
|
};
|
|
361
376
|
|
|
377
|
+
const isProtocolV2InstallResponseTimeout = (error: unknown) => {
|
|
378
|
+
if (!error || typeof error !== 'object') return false;
|
|
379
|
+
const { code } = error as { code?: number | string };
|
|
380
|
+
return (
|
|
381
|
+
code === 'response-timeout' ||
|
|
382
|
+
/\bresponse timeout\b/i.test(getProtocolV2UnknownErrorText(error))
|
|
383
|
+
);
|
|
384
|
+
};
|
|
385
|
+
|
|
362
386
|
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
363
|
-
error
|
|
364
|
-
(error
|
|
365
|
-
error.errorCode === HardwareErrorCode.
|
|
366
|
-
|
|
367
|
-
|
|
387
|
+
isBleStaleBondHardwareError(error) ||
|
|
388
|
+
(error instanceof HardwareError &&
|
|
389
|
+
(error.errorCode === HardwareErrorCode.ActionCancelled ||
|
|
390
|
+
error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
391
|
+
error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
|
|
392
|
+
error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE));
|
|
368
393
|
|
|
369
394
|
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
370
395
|
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
@@ -401,6 +426,18 @@ const normalizeProtocolV2TargetStatus = (
|
|
|
401
426
|
return undefined;
|
|
402
427
|
};
|
|
403
428
|
|
|
429
|
+
const PROTOCOL_V2_INSTALL_PHASE_BY_DECODED_VALUE = new Map<
|
|
430
|
+
number | string,
|
|
431
|
+
'prepare' | 'install' | 'verify'
|
|
432
|
+
>([
|
|
433
|
+
[0, 'prepare'],
|
|
434
|
+
[1, 'install'],
|
|
435
|
+
[2, 'verify'],
|
|
436
|
+
['FW_MGMT_UPDATER_PHASE_PREPARE', 'prepare'],
|
|
437
|
+
['FW_MGMT_UPDATER_PHASE_INSTALL', 'install'],
|
|
438
|
+
['FW_MGMT_UPDATER_PHASE_VERIFY', 'verify'],
|
|
439
|
+
]);
|
|
440
|
+
|
|
404
441
|
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
405
442
|
|
|
406
443
|
const versionArrayToNumber = (version?: IVersionArray) => {
|
|
@@ -458,6 +495,11 @@ const toProtocolV2FiniteNumber = (value: unknown) => {
|
|
|
458
495
|
return undefined;
|
|
459
496
|
};
|
|
460
497
|
|
|
498
|
+
const normalizeProtocolV2ProgressPercent = (value: unknown) => {
|
|
499
|
+
const progress = toProtocolV2FiniteNumber(value);
|
|
500
|
+
return progress === undefined ? undefined : Math.min(100, Math.max(0, progress));
|
|
501
|
+
};
|
|
502
|
+
|
|
461
503
|
export const isProtocolV2FirmwareFingerprintValid = (
|
|
462
504
|
binary: ArrayBuffer | Uint8Array,
|
|
463
505
|
fingerprint: string | undefined
|
|
@@ -530,6 +572,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
530
572
|
|
|
531
573
|
private protocolV2InstallBaselineVersions = new Map<number, string>();
|
|
532
574
|
|
|
575
|
+
private protocolV2InstallNeedsReconnect = false;
|
|
576
|
+
|
|
577
|
+
private protocolV2InstallDisconnectObserved = false;
|
|
578
|
+
|
|
579
|
+
private protocolV2InstallTerminalSuccessObserved = false;
|
|
580
|
+
|
|
533
581
|
private protocolV2LastRuntimeProbeFeatures?: Features;
|
|
534
582
|
|
|
535
583
|
private protocolV2LastTransferProgress?: number;
|
|
@@ -1808,6 +1856,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1808
1856
|
if (!expectedHeaderHash || header.headerHash !== expectedHeaderHash) return false;
|
|
1809
1857
|
return true;
|
|
1810
1858
|
} catch (error) {
|
|
1859
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
1860
|
+
throw error;
|
|
1861
|
+
}
|
|
1811
1862
|
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
1812
1863
|
return false;
|
|
1813
1864
|
}
|
|
@@ -1901,7 +1952,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1901
1952
|
}
|
|
1902
1953
|
lastError = new Error('Protocol V2 device is reachable but is not in bootloader mode');
|
|
1903
1954
|
} catch (error) {
|
|
1904
|
-
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1955
|
+
if (isBleStaleBondHardwareError(error) || this.isProtocolV2ReconnectIdentityError(error)) {
|
|
1905
1956
|
throw error;
|
|
1906
1957
|
}
|
|
1907
1958
|
shouldReconnect = true;
|
|
@@ -2183,6 +2234,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2183
2234
|
});
|
|
2184
2235
|
return processedSize + source.size;
|
|
2185
2236
|
} catch (error) {
|
|
2237
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
2238
|
+
throw error;
|
|
2239
|
+
}
|
|
2186
2240
|
lastError = error;
|
|
2187
2241
|
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
2188
2242
|
await this.recoverProtocolV2FileTransfer();
|
|
@@ -2212,19 +2266,41 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2212
2266
|
}
|
|
2213
2267
|
}
|
|
2214
2268
|
|
|
2269
|
+
private collectProtocolV2LiveTargetIds(
|
|
2270
|
+
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
2271
|
+
expectedTargetIds: Set<number>,
|
|
2272
|
+
liveTargetIds: Set<number>
|
|
2273
|
+
) {
|
|
2274
|
+
statusTargets.forEach(target => {
|
|
2275
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2276
|
+
if (
|
|
2277
|
+
targetId !== undefined &&
|
|
2278
|
+
expectedTargetIds.has(targetId) &&
|
|
2279
|
+
isProtocolV2TargetStatusInProgress(target.status)
|
|
2280
|
+
) {
|
|
2281
|
+
liveTargetIds.add(targetId);
|
|
2282
|
+
}
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2215
2286
|
private assertProtocolV2TargetStatus(
|
|
2216
2287
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
2217
2288
|
expectedTargetIds: Set<number>,
|
|
2218
|
-
expectedPaths
|
|
2289
|
+
expectedPaths?: Map<number, string>,
|
|
2290
|
+
liveTargetIds?: Set<number>
|
|
2219
2291
|
) {
|
|
2292
|
+
const resolvedExpectedPaths = expectedPaths ?? new Map<number, string>();
|
|
2220
2293
|
Log.log(
|
|
2221
2294
|
`[FirmwareUpdateV4] DeviceFirmwareUpdateStatus records=${JSON.stringify(statusTargets)}`
|
|
2222
2295
|
);
|
|
2223
|
-
const failedTarget = statusTargets.find(
|
|
2224
|
-
target
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2296
|
+
const failedTarget = statusTargets.find(target => {
|
|
2297
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id) ?? -1;
|
|
2298
|
+
return (
|
|
2299
|
+
expectedTargetIds.has(targetId) &&
|
|
2300
|
+
isProtocolV2TargetStatusFailed(target.status) &&
|
|
2301
|
+
(!liveTargetIds || liveTargetIds.has(targetId))
|
|
2302
|
+
);
|
|
2303
|
+
});
|
|
2228
2304
|
if (failedTarget) {
|
|
2229
2305
|
const failedTargetDetails = JSON.stringify({
|
|
2230
2306
|
targetId: failedTarget.target_id,
|
|
@@ -2241,6 +2317,27 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2241
2317
|
}
|
|
2242
2318
|
);
|
|
2243
2319
|
}
|
|
2320
|
+
if (liveTargetIds) {
|
|
2321
|
+
const staleFailedTargets = statusTargets.filter(target => {
|
|
2322
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id) ?? -1;
|
|
2323
|
+
return (
|
|
2324
|
+
expectedTargetIds.has(targetId) &&
|
|
2325
|
+
isProtocolV2TargetStatusFailed(target.status) &&
|
|
2326
|
+
!liveTargetIds.has(targetId)
|
|
2327
|
+
);
|
|
2328
|
+
});
|
|
2329
|
+
if (staleFailedTargets.length > 0) {
|
|
2330
|
+
Log.log(
|
|
2331
|
+
`[FirmwareUpdateV4] ignoring stale failed firmware status until those targets start: ${JSON.stringify(
|
|
2332
|
+
staleFailedTargets.map(target => ({
|
|
2333
|
+
targetId: target.target_id,
|
|
2334
|
+
status: target.status,
|
|
2335
|
+
path: target.path,
|
|
2336
|
+
}))
|
|
2337
|
+
)}`
|
|
2338
|
+
);
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2244
2341
|
|
|
2245
2342
|
const matchingTargets = statusTargets.filter(target =>
|
|
2246
2343
|
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1)
|
|
@@ -2248,10 +2345,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2248
2345
|
const seenTargetIds = new Set<number>();
|
|
2249
2346
|
for (const target of matchingTargets) {
|
|
2250
2347
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2348
|
+
const pathConflictsWithCurrentInstall =
|
|
2349
|
+
targetId !== undefined &&
|
|
2350
|
+
Boolean(target.path) &&
|
|
2351
|
+
Boolean(resolvedExpectedPaths.get(targetId)) &&
|
|
2352
|
+
target.path !== resolvedExpectedPaths.get(targetId) &&
|
|
2353
|
+
(!liveTargetIds || liveTargetIds.has(targetId));
|
|
2251
2354
|
if (
|
|
2252
2355
|
targetId === undefined ||
|
|
2253
2356
|
seenTargetIds.has(targetId) ||
|
|
2254
|
-
|
|
2357
|
+
pathConflictsWithCurrentInstall
|
|
2255
2358
|
) {
|
|
2256
2359
|
Log.error(`[FirmwareUpdateV4] install status conflicts with target=${target.target_id}`);
|
|
2257
2360
|
throw ERRORS.TypedError(
|
|
@@ -2264,9 +2367,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2264
2367
|
}
|
|
2265
2368
|
seenTargetIds.add(targetId);
|
|
2266
2369
|
}
|
|
2267
|
-
const completedTargets = matchingTargets.filter(target =>
|
|
2268
|
-
|
|
2269
|
-
|
|
2370
|
+
const completedTargets = matchingTargets.filter(target => {
|
|
2371
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2372
|
+
return (
|
|
2373
|
+
isProtocolV2TargetStatusFinished(target.status) &&
|
|
2374
|
+
(!liveTargetIds || (targetId !== undefined && liveTargetIds.has(targetId)))
|
|
2375
|
+
);
|
|
2376
|
+
});
|
|
2270
2377
|
const completedTargetIds = new Set<number>();
|
|
2271
2378
|
completedTargets.forEach(target => {
|
|
2272
2379
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
@@ -2303,16 +2410,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2303
2410
|
}
|
|
2304
2411
|
|
|
2305
2412
|
if (expectedTargetIds.size > 0 && matchingTargets.length > 0) {
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2413
|
+
let detailedProgress = completedTargetIds.size * 100;
|
|
2414
|
+
let installProgressMetadata:
|
|
2415
|
+
| {
|
|
2416
|
+
installTargetId: number;
|
|
2417
|
+
installPhase: 'prepare' | 'install' | 'verify';
|
|
2418
|
+
installPhaseProgress: number;
|
|
2419
|
+
}
|
|
2420
|
+
| undefined;
|
|
2421
|
+
let hasInProgressTarget = false;
|
|
2422
|
+
matchingTargets.forEach(target => {
|
|
2423
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2424
|
+
if (
|
|
2425
|
+
targetId === undefined ||
|
|
2426
|
+
completedTargetIds.has(targetId) ||
|
|
2427
|
+
!isProtocolV2TargetStatusInProgress(target.status) ||
|
|
2428
|
+
(liveTargetIds && !liveTargetIds.has(targetId))
|
|
2429
|
+
) {
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
hasInProgressTarget = true;
|
|
2433
|
+
detailedProgress += normalizeProtocolV2ProgressPercent(target.progress_percent) ?? 0;
|
|
2434
|
+
const installPhase = target.phase_info
|
|
2435
|
+
? PROTOCOL_V2_INSTALL_PHASE_BY_DECODED_VALUE.get(target.phase_info.phase)
|
|
2436
|
+
: undefined;
|
|
2437
|
+
const installPhaseProgress = normalizeProtocolV2ProgressPercent(
|
|
2438
|
+
target.phase_info?.progress_percent
|
|
2439
|
+
);
|
|
2440
|
+
if (installPhase && installPhaseProgress !== undefined && !installProgressMetadata) {
|
|
2441
|
+
installProgressMetadata = {
|
|
2442
|
+
installTargetId: targetId,
|
|
2443
|
+
installPhase,
|
|
2444
|
+
installPhaseProgress,
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
});
|
|
2309
2448
|
const completedProgress = Math.floor(
|
|
2310
2449
|
(completedTargetIds.size / expectedTargetIds.size) * 100
|
|
2311
2450
|
);
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2451
|
+
const reportedProgress = Math.floor(detailedProgress / expectedTargetIds.size);
|
|
2452
|
+
const progress = Math.min(
|
|
2453
|
+
99,
|
|
2454
|
+
Math.max(completedProgress, reportedProgress, hasInProgressTarget ? 1 : 0)
|
|
2455
|
+
);
|
|
2456
|
+
if (installProgressMetadata) {
|
|
2457
|
+
this.postProgressMessage(progress, 'installingFirmware', installProgressMetadata);
|
|
2458
|
+
} else {
|
|
2459
|
+
this.postProgressMessage(progress, 'installingFirmware');
|
|
2460
|
+
}
|
|
2316
2461
|
}
|
|
2317
2462
|
|
|
2318
2463
|
return false;
|
|
@@ -2383,14 +2528,37 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2383
2528
|
) {
|
|
2384
2529
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
2385
2530
|
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
2531
|
+
const isBleInstall = this.isBleReconnect();
|
|
2386
2532
|
const startTime = Date.now();
|
|
2387
2533
|
let lastError: unknown;
|
|
2388
|
-
//
|
|
2389
|
-
//
|
|
2390
|
-
let shouldReconnect =
|
|
2534
|
+
// The loader may release either USB or BLE as installation starts. Recovery reconnects
|
|
2535
|
+
// directly to status polling without generic Ping or DeviceInfo probes.
|
|
2536
|
+
let shouldReconnect = this.protocolV2InstallNeedsReconnect;
|
|
2537
|
+
this.protocolV2InstallNeedsReconnect = false;
|
|
2391
2538
|
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
2392
|
-
let
|
|
2539
|
+
let bleInstallLinkReady = false;
|
|
2540
|
+
let installStatusDisconnectObserved = isBleInstall && this.protocolV2InstallDisconnectObserved;
|
|
2541
|
+
this.protocolV2InstallDisconnectObserved = false;
|
|
2542
|
+
let installReconnectObserved = false;
|
|
2543
|
+
let finishedStatusSnapshotKey: string | undefined;
|
|
2544
|
+
let finishedStatusSnapshotPolls = 0;
|
|
2545
|
+
let fastFinishedInstallProgressReported = false;
|
|
2546
|
+
let installEvidenceObserved = this.protocolV2InstallTerminalSuccessObserved;
|
|
2393
2547
|
let currentInstallStatusObserved = false;
|
|
2548
|
+
const liveTargetIds = new Set<number>();
|
|
2549
|
+
const recordBleInstallDisconnect = (error: unknown) => {
|
|
2550
|
+
if (
|
|
2551
|
+
!isBleInstall ||
|
|
2552
|
+
!isProtocolV2DeviceDisconnectedError(error) ||
|
|
2553
|
+
isProtocolV2InstallResponseTimeout(error)
|
|
2554
|
+
) {
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
installStatusDisconnectObserved = true;
|
|
2558
|
+
installReconnectObserved = false;
|
|
2559
|
+
finishedStatusSnapshotKey = undefined;
|
|
2560
|
+
finishedStatusSnapshotPolls = 0;
|
|
2561
|
+
};
|
|
2394
2562
|
|
|
2395
2563
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
2396
2564
|
// A transport release caused by an explicit workflow cancellation must not
|
|
@@ -2398,43 +2566,40 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2398
2566
|
this.throwIfAborted();
|
|
2399
2567
|
try {
|
|
2400
2568
|
if (shouldReconnect) {
|
|
2401
|
-
|
|
2402
|
-
|
|
2569
|
+
const isBleInstallReconnect = this.isBleReconnect();
|
|
2570
|
+
await this.reconnectProtocolV2Device({ skipProtocolProbe: true });
|
|
2571
|
+
bleInstallLinkReady = isBleInstallReconnect;
|
|
2572
|
+
deviceInfo = isBleInstallReconnect
|
|
2573
|
+
? undefined
|
|
2574
|
+
: await this.verifyProtocolV2ReconnectIdentity();
|
|
2575
|
+
if (isBleInstallReconnect && installStatusDisconnectObserved) {
|
|
2576
|
+
installReconnectObserved = true;
|
|
2577
|
+
}
|
|
2403
2578
|
shouldReconnect = false;
|
|
2404
2579
|
}
|
|
2405
|
-
|
|
2580
|
+
let currentDeviceInfo = deviceInfo;
|
|
2406
2581
|
try {
|
|
2407
2582
|
const statusResponse = await this.device.getCommands().typedCall(
|
|
2408
2583
|
'DeviceFirmwareUpdateStatusGet',
|
|
2409
|
-
|
|
2584
|
+
'DeviceFirmwareUpdateStatus',
|
|
2410
2585
|
{
|
|
2411
2586
|
fields: {
|
|
2412
2587
|
status: true,
|
|
2588
|
+
progress_percent: true,
|
|
2589
|
+
phase_info: true,
|
|
2413
2590
|
payload_version: true,
|
|
2414
2591
|
path: true,
|
|
2415
2592
|
},
|
|
2416
2593
|
},
|
|
2417
2594
|
{ timeoutMs: PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT }
|
|
2418
2595
|
);
|
|
2419
|
-
if (
|
|
2596
|
+
if (this.protocolV2InstallTerminalSuccessObserved) {
|
|
2420
2597
|
installEvidenceObserved = true;
|
|
2421
|
-
lastError = new Error(
|
|
2422
|
-
'Protocol V2 firmware install acknowledged; waiting for target status'
|
|
2423
|
-
);
|
|
2424
2598
|
}
|
|
2425
|
-
const statusTargets =
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
const hasInProgressTarget = statusTargets.some(target => {
|
|
2430
|
-
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2431
|
-
return (
|
|
2432
|
-
targetId !== undefined &&
|
|
2433
|
-
expectedTargetIds.has(targetId) &&
|
|
2434
|
-
isProtocolV2TargetStatusInProgress(target.status)
|
|
2435
|
-
);
|
|
2436
|
-
});
|
|
2437
|
-
if (hasInProgressTarget) {
|
|
2599
|
+
const statusTargets = (statusResponse.message.records ??
|
|
2600
|
+
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
2601
|
+
this.collectProtocolV2LiveTargetIds(statusTargets, expectedTargetIds, liveTargetIds);
|
|
2602
|
+
if (liveTargetIds.size > 0) {
|
|
2438
2603
|
currentInstallStatusObserved = true;
|
|
2439
2604
|
}
|
|
2440
2605
|
const hasMatchingTargetStatus = statusTargets.some(target => {
|
|
@@ -2451,18 +2616,101 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2451
2616
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2452
2617
|
return targetId !== undefined && expectedTargetIds.has(targetId);
|
|
2453
2618
|
});
|
|
2619
|
+
const matchingFinishedTargetIds = new Set<number>();
|
|
2620
|
+
const matchingFinishedStatus = matchingStatusTargets
|
|
2621
|
+
.map(target => {
|
|
2622
|
+
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
2623
|
+
const expectedPath = targetId === undefined ? undefined : expectedPaths.get(targetId);
|
|
2624
|
+
const expectedVersion =
|
|
2625
|
+
targetId === undefined
|
|
2626
|
+
? undefined
|
|
2627
|
+
: this.getExpectedProtocolV2TargetVersion(targetId);
|
|
2628
|
+
const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
|
|
2629
|
+
if (
|
|
2630
|
+
targetId === undefined ||
|
|
2631
|
+
matchingFinishedTargetIds.has(targetId) ||
|
|
2632
|
+
!isProtocolV2TargetStatusFinished(target.status) ||
|
|
2633
|
+
(Boolean(target.path) && Boolean(expectedPath) && target.path !== expectedPath) ||
|
|
2634
|
+
(expectedVersion !== undefined &&
|
|
2635
|
+
payloadVersion !== undefined &&
|
|
2636
|
+
payloadVersion !== expectedVersion)
|
|
2637
|
+
) {
|
|
2638
|
+
return undefined;
|
|
2639
|
+
}
|
|
2640
|
+
matchingFinishedTargetIds.add(targetId);
|
|
2641
|
+
return [targetId, target.path ?? null, payloadVersion ?? null] as const;
|
|
2642
|
+
})
|
|
2643
|
+
.filter((target): target is readonly [number, string | null, number | null] => !!target)
|
|
2644
|
+
.sort(([leftTargetId], [rightTargetId]) => leftTargetId - rightTargetId);
|
|
2645
|
+
const hasCompleteMatchingFinishedStatus =
|
|
2646
|
+
matchingFinishedStatus.length === expectedTargetIds.size &&
|
|
2647
|
+
matchingFinishedTargetIds.size === expectedTargetIds.size &&
|
|
2648
|
+
expectedTargetIds.size > 0;
|
|
2649
|
+
if (
|
|
2650
|
+
requireCurrentInstallStatus &&
|
|
2651
|
+
installReconnectObserved &&
|
|
2652
|
+
hasCompleteMatchingFinishedStatus
|
|
2653
|
+
) {
|
|
2654
|
+
const nextSnapshotKey = JSON.stringify(matchingFinishedStatus);
|
|
2655
|
+
if (nextSnapshotKey === finishedStatusSnapshotKey) {
|
|
2656
|
+
finishedStatusSnapshotPolls += 1;
|
|
2657
|
+
} else {
|
|
2658
|
+
finishedStatusSnapshotKey = nextSnapshotKey;
|
|
2659
|
+
finishedStatusSnapshotPolls = 1;
|
|
2660
|
+
}
|
|
2661
|
+
} else {
|
|
2662
|
+
finishedStatusSnapshotKey = undefined;
|
|
2663
|
+
finishedStatusSnapshotPolls = 0;
|
|
2664
|
+
}
|
|
2665
|
+
if (
|
|
2666
|
+
requireCurrentInstallStatus &&
|
|
2667
|
+
installReconnectObserved &&
|
|
2668
|
+
hasCompleteMatchingFinishedStatus &&
|
|
2669
|
+
!currentInstallStatusObserved &&
|
|
2670
|
+
!this.protocolV2InstallTerminalSuccessObserved &&
|
|
2671
|
+
!fastFinishedInstallProgressReported
|
|
2672
|
+
) {
|
|
2673
|
+
// A fast BLE install may finish while disconnected, so IN_PROGRESS can be hidden.
|
|
2674
|
+
// A status response timeout alone is not evidence that the install was accepted.
|
|
2675
|
+
this.postProgressMessage(1, 'installingFirmware');
|
|
2676
|
+
fastFinishedInstallProgressReported = true;
|
|
2677
|
+
}
|
|
2678
|
+
const finishedAfterDisconnectObserved =
|
|
2679
|
+
finishedStatusSnapshotPolls >= PROTOCOL_V2_INSTALL_FINISHED_AFTER_DISCONNECT_POLLS;
|
|
2680
|
+
const hasCurrentInstallEvidence =
|
|
2681
|
+
currentInstallStatusObserved ||
|
|
2682
|
+
this.protocolV2InstallTerminalSuccessObserved ||
|
|
2683
|
+
finishedAfterDisconnectObserved;
|
|
2684
|
+
if (hasCurrentInstallEvidence) {
|
|
2685
|
+
installEvidenceObserved = true;
|
|
2686
|
+
}
|
|
2454
2687
|
const shouldVerifyTargetCompletion =
|
|
2455
|
-
!requireCurrentInstallStatus ||
|
|
2688
|
+
!requireCurrentInstallStatus || hasCurrentInstallEvidence;
|
|
2456
2689
|
if (
|
|
2457
2690
|
shouldVerifyTargetCompletion &&
|
|
2458
|
-
this.assertProtocolV2TargetStatus(
|
|
2691
|
+
this.assertProtocolV2TargetStatus(
|
|
2692
|
+
statusTargets,
|
|
2693
|
+
expectedTargetIds,
|
|
2694
|
+
expectedPaths,
|
|
2695
|
+
requireCurrentInstallStatus &&
|
|
2696
|
+
!this.protocolV2InstallTerminalSuccessObserved &&
|
|
2697
|
+
!finishedAfterDisconnectObserved
|
|
2698
|
+
? liveTargetIds
|
|
2699
|
+
: undefined
|
|
2700
|
+
)
|
|
2459
2701
|
) {
|
|
2460
2702
|
return;
|
|
2461
2703
|
}
|
|
2462
2704
|
|
|
2705
|
+
if (!hasCurrentInstallEvidence) {
|
|
2706
|
+
// A successful status poll clears DeviceCommands' cancel action. Keep cancellation
|
|
2707
|
+
// available while UpdateRequest is still waiting for confirmation on the device.
|
|
2708
|
+
this.device.setCancelableAction(() => this.device.getCommands().cancelDevice());
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2463
2711
|
if (
|
|
2464
2712
|
requireCurrentInstallStatus &&
|
|
2465
|
-
!
|
|
2713
|
+
!hasCurrentInstallEvidence &&
|
|
2466
2714
|
matchingStatusTargets.length > 0
|
|
2467
2715
|
) {
|
|
2468
2716
|
lastError = new Error(
|
|
@@ -2470,6 +2718,18 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2470
2718
|
);
|
|
2471
2719
|
}
|
|
2472
2720
|
|
|
2721
|
+
if (
|
|
2722
|
+
statusTargets.length === 0 &&
|
|
2723
|
+
!currentDeviceInfo &&
|
|
2724
|
+
bleInstallLinkReady &&
|
|
2725
|
+
installEvidenceObserved
|
|
2726
|
+
) {
|
|
2727
|
+
// BLE install reconnect skips generic probes because loaders may not answer Ping.
|
|
2728
|
+
// Restore the verified identity only after current-install evidence disappears.
|
|
2729
|
+
currentDeviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
2730
|
+
deviceInfo = currentDeviceInfo;
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2473
2733
|
if (statusTargets.length === 0 && currentDeviceInfo) {
|
|
2474
2734
|
const isNormalMode = await this.probeProtocolV2NormalMode(currentDeviceInfo);
|
|
2475
2735
|
if (
|
|
@@ -2487,11 +2747,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2487
2747
|
}
|
|
2488
2748
|
|
|
2489
2749
|
if (statusTargets.length === 0) {
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
);
|
|
2494
|
-
}
|
|
2750
|
+
lastError = new Error(
|
|
2751
|
+
'Protocol V2 firmware update is waiting for user confirmation or target status'
|
|
2752
|
+
);
|
|
2495
2753
|
} else {
|
|
2496
2754
|
const missingTargetIds = this.getProtocolV2MissingTargetIds(
|
|
2497
2755
|
statusTargets,
|
|
@@ -2514,12 +2772,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2514
2772
|
// missing endpoint as completion only after the runtime probe confirms App mode.
|
|
2515
2773
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
2516
2774
|
if (!currentDeviceInfo) {
|
|
2775
|
+
if (!bleInstallLinkReady) {
|
|
2776
|
+
throw ERRORS.TypedError(
|
|
2777
|
+
HardwareErrorCode.RuntimeError,
|
|
2778
|
+
'Protocol V2 device identity is unavailable during install polling'
|
|
2779
|
+
);
|
|
2780
|
+
}
|
|
2781
|
+
deviceInfo = await this.verifyProtocolV2ReconnectIdentity();
|
|
2782
|
+
}
|
|
2783
|
+
const reconnectDeviceInfo = currentDeviceInfo ?? deviceInfo;
|
|
2784
|
+
if (!reconnectDeviceInfo) {
|
|
2517
2785
|
throw ERRORS.TypedError(
|
|
2518
2786
|
HardwareErrorCode.RuntimeError,
|
|
2519
2787
|
'Protocol V2 device identity is unavailable during install polling'
|
|
2520
2788
|
);
|
|
2521
2789
|
}
|
|
2522
|
-
const isNormalMode = await this.probeProtocolV2NormalMode(
|
|
2790
|
+
const isNormalMode = await this.probeProtocolV2NormalMode(reconnectDeviceInfo);
|
|
2523
2791
|
if (
|
|
2524
2792
|
isNormalMode &&
|
|
2525
2793
|
(installEvidenceObserved ||
|
|
@@ -2542,8 +2810,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2542
2810
|
);
|
|
2543
2811
|
}
|
|
2544
2812
|
} else {
|
|
2813
|
+
recordBleInstallDisconnect(error);
|
|
2545
2814
|
shouldReconnect = true;
|
|
2546
2815
|
deviceInfo = undefined;
|
|
2816
|
+
bleInstallLinkReady = false;
|
|
2547
2817
|
lastError = error;
|
|
2548
2818
|
Log.log(
|
|
2549
2819
|
'[FirmwareUpdateV4] DeviceFirmwareUpdateStatusGet unavailable during install: ',
|
|
@@ -2565,8 +2835,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2565
2835
|
) {
|
|
2566
2836
|
throw error;
|
|
2567
2837
|
}
|
|
2838
|
+
recordBleInstallDisconnect(error);
|
|
2568
2839
|
shouldReconnect = true;
|
|
2569
2840
|
deviceInfo = undefined;
|
|
2841
|
+
bleInstallLinkReady = false;
|
|
2570
2842
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
2571
2843
|
}
|
|
2572
2844
|
await wait(1000);
|
|
@@ -2595,6 +2867,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2595
2867
|
return;
|
|
2596
2868
|
}
|
|
2597
2869
|
} catch (error) {
|
|
2870
|
+
if (isBleStaleBondHardwareError(error)) {
|
|
2871
|
+
throw error;
|
|
2872
|
+
}
|
|
2598
2873
|
Log.log('[FirmwareUpdateV4] unable to confirm App mode before Normal reboot: ', error);
|
|
2599
2874
|
}
|
|
2600
2875
|
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
@@ -2686,7 +2961,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2686
2961
|
'Protocol V2 device is still in bootloader mode'
|
|
2687
2962
|
);
|
|
2688
2963
|
} catch (error) {
|
|
2689
|
-
if (this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2964
|
+
if (isBleStaleBondHardwareError(error) || this.isProtocolV2ReconnectIdentityError(error)) {
|
|
2690
2965
|
throw error;
|
|
2691
2966
|
}
|
|
2692
2967
|
shouldReconnect = true;
|
|
@@ -2704,19 +2979,53 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2704
2979
|
);
|
|
2705
2980
|
}
|
|
2706
2981
|
|
|
2707
|
-
private async reconnectProtocolV2Device() {
|
|
2982
|
+
private async reconnectProtocolV2Device(options?: { skipProtocolProbe?: boolean }) {
|
|
2708
2983
|
if (this.isBleReconnect()) {
|
|
2709
|
-
await this.acquireProtocolV2BleDevice();
|
|
2984
|
+
await this.acquireProtocolV2BleDevice(options?.skipProtocolProbe);
|
|
2710
2985
|
return;
|
|
2711
2986
|
}
|
|
2712
2987
|
|
|
2713
2988
|
const deviceDiff = await this.device.deviceConnector?.enumerate();
|
|
2714
2989
|
const devicesDescriptor = deviceDiff?.descriptors ?? [];
|
|
2990
|
+
const env = DataManager.getSettings('env');
|
|
2991
|
+
const isWebUsb = DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env);
|
|
2992
|
+
|
|
2993
|
+
if (isWebUsb && options?.skipProtocolProbe) {
|
|
2994
|
+
const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
|
|
2995
|
+
const expectedPath = this.protocolV2ExpectedPath?.trim();
|
|
2996
|
+
const reconnectDescriptor = devicesDescriptor.find(descriptor => {
|
|
2997
|
+
const descriptorPath = descriptor.path?.trim();
|
|
2998
|
+
return (
|
|
2999
|
+
(!!expectedSerialNumber && descriptorPath === expectedSerialNumber) ||
|
|
3000
|
+
(!!expectedPath && descriptorPath === expectedPath)
|
|
3001
|
+
);
|
|
3002
|
+
});
|
|
3003
|
+
if (!reconnectDescriptor?.path) {
|
|
3004
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
3005
|
+
}
|
|
3006
|
+
this.device.updateDescriptor(
|
|
3007
|
+
{
|
|
3008
|
+
...reconnectDescriptor,
|
|
3009
|
+
protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
3010
|
+
},
|
|
3011
|
+
true
|
|
3012
|
+
);
|
|
3013
|
+
const mainId = await this.device.deviceConnector?.acquire(
|
|
3014
|
+
reconnectDescriptor.path,
|
|
3015
|
+
null,
|
|
3016
|
+
undefined,
|
|
3017
|
+
PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
3018
|
+
undefined,
|
|
3019
|
+
undefined,
|
|
3020
|
+
true
|
|
3021
|
+
);
|
|
3022
|
+
this.device.mainId = typeof mainId === 'string' ? mainId : reconnectDescriptor.path;
|
|
3023
|
+
this.device.commands.disposed = false;
|
|
3024
|
+
this.device.getCommands().mainId = this.device.mainId;
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
2715
3027
|
|
|
2716
|
-
if (
|
|
2717
|
-
DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
|
|
2718
|
-
devicesDescriptor.length === 1
|
|
2719
|
-
) {
|
|
3028
|
+
if (isWebUsb && devicesDescriptor.length === 1) {
|
|
2720
3029
|
const descriptor = devicesDescriptor[0];
|
|
2721
3030
|
if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
|
|
2722
3031
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
@@ -2834,13 +3143,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2834
3143
|
await wait(2000);
|
|
2835
3144
|
}
|
|
2836
3145
|
|
|
2837
|
-
private async acquireProtocolV2BleDevice() {
|
|
2838
|
-
|
|
2839
|
-
|
|
3146
|
+
private async acquireProtocolV2BleDevice(skipProtocolProbe = false) {
|
|
3147
|
+
const connector = this.device.deviceConnector;
|
|
3148
|
+
const expectedId = this.device.originalDescriptor.id;
|
|
3149
|
+
if (!skipProtocolProbe) {
|
|
3150
|
+
await connector?.acquire(expectedId, null, true, PROTOCOL_V2_CONNECT_PROTOCOL);
|
|
3151
|
+
return;
|
|
3152
|
+
}
|
|
3153
|
+
await connector?.acquire(
|
|
3154
|
+
expectedId,
|
|
2840
3155
|
null,
|
|
2841
3156
|
true,
|
|
2842
|
-
PROTOCOL_V2_CONNECT_PROTOCOL
|
|
3157
|
+
PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
3158
|
+
undefined,
|
|
3159
|
+
undefined,
|
|
3160
|
+
skipProtocolProbe
|
|
2843
3161
|
);
|
|
3162
|
+
this.device.commands.disposed = false;
|
|
3163
|
+
this.device.getCommands().mainId = expectedId;
|
|
2844
3164
|
}
|
|
2845
3165
|
|
|
2846
3166
|
private async protocolV2StartFirmwareUpdate({
|
|
@@ -2849,9 +3169,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2849
3169
|
targets: Array<{ target_id: number; path: string }>;
|
|
2850
3170
|
}) {
|
|
2851
3171
|
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
3172
|
+
this.protocolV2InstallNeedsReconnect = false;
|
|
3173
|
+
this.protocolV2InstallDisconnectObserved = false;
|
|
3174
|
+
this.protocolV2InstallTerminalSuccessObserved = false;
|
|
2852
3175
|
const commands = this.device.getCommands();
|
|
2853
3176
|
await commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
|
|
2854
|
-
this.device.setCancelableAction(() =>
|
|
3177
|
+
this.device.setCancelableAction(() => this.device.getCommands().cancelDevice());
|
|
2855
3178
|
const interaction = this.device.createProtocolV2UiPhaseMetadata('button', 'start');
|
|
2856
3179
|
this.postMessage(
|
|
2857
3180
|
createUiMessage(UI_REQUEST.REQUEST_BUTTON, {
|
|
@@ -2864,19 +3187,55 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2864
3187
|
...(interaction ? { interaction } : {}),
|
|
2865
3188
|
})
|
|
2866
3189
|
);
|
|
2867
|
-
|
|
3190
|
+
|
|
3191
|
+
try {
|
|
3192
|
+
await commands.call(
|
|
3193
|
+
'DeviceFirmwareUpdateRequest',
|
|
3194
|
+
{},
|
|
3195
|
+
{
|
|
3196
|
+
returnAfterWrite: true,
|
|
3197
|
+
expectedTypes: ['Success'],
|
|
3198
|
+
onResponseAfterWrite: response => {
|
|
3199
|
+
if (response.type !== 'Success') return;
|
|
3200
|
+
this.protocolV2InstallTerminalSuccessObserved = true;
|
|
3201
|
+
this.device.clearCancelableAction();
|
|
3202
|
+
Log.log('[FirmwareUpdateV4] firmware install completed by device response');
|
|
3203
|
+
},
|
|
3204
|
+
}
|
|
3205
|
+
);
|
|
3206
|
+
Log.log('[FirmwareUpdateV4] firmware install request written; continue with status polling');
|
|
3207
|
+
} catch (error) {
|
|
3208
|
+
this.throwIfAborted();
|
|
3209
|
+
if (!isProtocolV2DeviceDisconnectedError(error)) {
|
|
3210
|
+
throw error;
|
|
3211
|
+
}
|
|
3212
|
+
Log.log(
|
|
3213
|
+
'[FirmwareUpdateV4] transport released while writing install request; continue status polling'
|
|
3214
|
+
);
|
|
3215
|
+
this.protocolV2InstallNeedsReconnect = true;
|
|
3216
|
+
this.protocolV2InstallDisconnectObserved = !isProtocolV2InstallResponseTimeout(error);
|
|
3217
|
+
}
|
|
2868
3218
|
}
|
|
2869
3219
|
|
|
2870
3220
|
private async protocolV2Reboot(rebootType: DeviceRebootType) {
|
|
2871
3221
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
2872
3222
|
try {
|
|
2873
|
-
const res = await typedCall(
|
|
2874
|
-
|
|
2875
|
-
|
|
3223
|
+
const res = await typedCall(
|
|
3224
|
+
'DeviceReboot',
|
|
3225
|
+
'Success',
|
|
3226
|
+
{
|
|
3227
|
+
reboot_type: rebootType,
|
|
3228
|
+
},
|
|
3229
|
+
{ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
|
|
3230
|
+
);
|
|
2876
3231
|
this.device.markProtocolV2Reboot(rebootType);
|
|
2877
3232
|
return res.message;
|
|
2878
3233
|
} catch (error) {
|
|
2879
|
-
if (
|
|
3234
|
+
if (
|
|
3235
|
+
isProtocolV2DeviceDisconnectedError(error) ||
|
|
3236
|
+
isProtocolV2ReconnectProbeError(error) ||
|
|
3237
|
+
isProtocolV2ResponseTimeout(error)
|
|
3238
|
+
) {
|
|
2880
3239
|
this.device.markProtocolV2Reboot(rebootType);
|
|
2881
3240
|
return { message: 'Device rebooted successfully' };
|
|
2882
3241
|
}
|