@onekeyfe/hd-core 1.2.0-alpha.75 → 1.2.0-alpha.76
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__/connectSettings.test.ts +45 -5
- package/__tests__/device-initialize-timeout.test.ts +56 -0
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +175 -0
- package/__tests__/firmware-update/device-update-bootloader.test.ts +87 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
- package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
- package/__tests__/firmware-update/firmware-preparation-error.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +200 -0
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +394 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +593 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +231 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +59 -31
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +64 -0
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/protocol-v2.test.ts +405 -100
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +10 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +7 -3
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +25 -5
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts +3 -0
- package/dist/api/firmware/FirmwarePreparationError.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +8 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +25 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +32 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/api/firmware/getBinary.d.ts +7 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/updateBootloader.d.ts +2 -0
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +9 -1
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/data-manager/DataManager.d.ts +1 -0
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/connectSettings.d.ts.map +1 -1
- package/dist/device/Device.d.ts +1 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/index.d.ts +179 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3079 -1062
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +4 -0
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +4 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +68 -0
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts +28 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/types/api/index.d.ts +9 -1
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/settings.d.ts +13 -0
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +1 -0
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +57 -6
- package/src/api/FirmwareUpdate.ts +13 -1
- package/src/api/FirmwareUpdateV2.ts +296 -121
- package/src/api/FirmwareUpdateV3.ts +263 -57
- package/src/api/FirmwareUpdateV4.ts +881 -349
- package/src/api/device/DeviceUpdateBootloader.ts +125 -6
- package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
- package/src/api/firmware/FirmwareHostBinding.ts +100 -0
- package/src/api/firmware/FirmwarePreparationError.ts +12 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +72 -1
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +772 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +449 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +140 -22
- package/src/data-manager/DataManager.ts +62 -54
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +6 -1
- package/src/index.ts +5 -0
- package/src/inject.ts +22 -2
- package/src/lowLevelInject.ts +5 -1
- package/src/topLevelInject.ts +5 -1
- package/src/types/api/checkAllFirmwareRelease.ts +4 -0
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -0
- package/src/types/api/firmwareUpdate.ts +75 -0
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +38 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/index.ts +14 -0
- package/src/types/settings.ts +13 -0
- package/src/utils/deviceFeaturesCompat.ts +6 -0
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
EDeviceType,
|
|
3
|
+
EFirmwareType,
|
|
4
|
+
ERRORS,
|
|
5
|
+
HardwareError,
|
|
6
|
+
HardwareErrorCode,
|
|
7
|
+
} from '@onekeyfe/hd-shared';
|
|
2
8
|
import semver from 'semver';
|
|
3
9
|
|
|
10
|
+
import { BaseMethod } from './BaseMethod';
|
|
4
11
|
import { UI_REQUEST } from '../constants/ui-request';
|
|
5
12
|
import { DataManager } from '../data-manager';
|
|
6
13
|
import {
|
|
@@ -8,13 +15,22 @@ import {
|
|
|
8
15
|
getBootloaderReleaseInfo,
|
|
9
16
|
getFirmwareReleaseInfo,
|
|
10
17
|
} from './firmware/releaseHelper';
|
|
18
|
+
import {
|
|
19
|
+
buildFirmwareUpdatePlan,
|
|
20
|
+
validateFirmwareUpdatePlanForceTargets,
|
|
21
|
+
} from './firmware/FirmwareUpdatePlan';
|
|
11
22
|
import { getBridgeReleaseInfo } from '../utils/bridgeUpdate';
|
|
12
|
-
import { getDeviceFirmwareVersion, getDeviceType, getFirmwareType } from '../utils';
|
|
13
|
-
import { BaseMethod } from './BaseMethod';
|
|
14
23
|
import {
|
|
15
24
|
buildProtocolV2ResourceUpdatePlan,
|
|
16
25
|
readProtocolV2ResourceInventory,
|
|
17
26
|
} from '../protocols/protocol-v2/resources';
|
|
27
|
+
import {
|
|
28
|
+
LoggerNames,
|
|
29
|
+
getDeviceFirmwareVersion,
|
|
30
|
+
getDeviceType,
|
|
31
|
+
getFirmwareType,
|
|
32
|
+
getLogger,
|
|
33
|
+
} from '../utils';
|
|
18
34
|
|
|
19
35
|
import type {
|
|
20
36
|
AllFirmwareRelease,
|
|
@@ -30,6 +46,7 @@ import type {
|
|
|
30
46
|
IProtocolV2FirmwareComponent,
|
|
31
47
|
IProtocolV2FirmwareComponentTarget,
|
|
32
48
|
} from '../types';
|
|
49
|
+
import type { FirmwareUpdatePlan } from '../types/api/firmwareUpdatePlan';
|
|
33
50
|
|
|
34
51
|
const UPDATE_TARGET_BY_COMPONENT_TARGET: Readonly<
|
|
35
52
|
Partial<Record<IProtocolV2FirmwareComponentTarget, FirmwareUpdateV4Target>>
|
|
@@ -266,6 +283,8 @@ export function buildProtocolV2FirmwareRelease({
|
|
|
266
283
|
};
|
|
267
284
|
}
|
|
268
285
|
|
|
286
|
+
const Log = getLogger(LoggerNames.Method);
|
|
287
|
+
|
|
269
288
|
export default class CheckAllFirmwareRelease extends BaseMethod {
|
|
270
289
|
getSupportedProtocols() {
|
|
271
290
|
return ['V1', 'V2'] as const;
|
|
@@ -283,8 +302,13 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
|
|
|
283
302
|
}
|
|
284
303
|
|
|
285
304
|
const { features } = this.device;
|
|
286
|
-
const {
|
|
287
|
-
|
|
305
|
+
const {
|
|
306
|
+
checkBridgeRelease,
|
|
307
|
+
firmwareType: firmwareTypeParams,
|
|
308
|
+
platform,
|
|
309
|
+
forceUpdateTargets,
|
|
310
|
+
} = this.payload as CheckAllFirmwareReleaseParams;
|
|
311
|
+
const validatedForceUpdateTargets = validateFirmwareUpdatePlanForceTargets(forceUpdateTargets);
|
|
288
312
|
|
|
289
313
|
if (!features) {
|
|
290
314
|
return Promise.resolve(null);
|
|
@@ -315,6 +339,32 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
|
|
|
315
339
|
firmwareType,
|
|
316
340
|
});
|
|
317
341
|
const bleFirmwareReleaseInfo = getBleFirmwareReleaseInfo(features);
|
|
342
|
+
let firmwareUpdatePlan: FirmwareUpdatePlan | undefined;
|
|
343
|
+
try {
|
|
344
|
+
firmwareUpdatePlan = buildFirmwareUpdatePlan({
|
|
345
|
+
features,
|
|
346
|
+
firmwareType,
|
|
347
|
+
platform: platform ?? 'web',
|
|
348
|
+
firmware: firmwareRelease,
|
|
349
|
+
ble: bleFirmwareReleaseInfo,
|
|
350
|
+
bootloader: bootloaderRelease,
|
|
351
|
+
forceUpdateTargets: validatedForceUpdateTargets,
|
|
352
|
+
});
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (
|
|
355
|
+
validatedForceUpdateTargets.length > 0 ||
|
|
356
|
+
!(error instanceof HardwareError) ||
|
|
357
|
+
error.params?.firmwareUpdateCode !== 'FirmwarePlanInvalid'
|
|
358
|
+
) {
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
// A Plan is an optional external-host capability. Release checks remain
|
|
362
|
+
// available for legacy and recovery flows when a Plan cannot be built.
|
|
363
|
+
Log.warn(
|
|
364
|
+
'[CheckAllFirmwareRelease] Optional firmware Plan is unavailable; using the legacy release result'
|
|
365
|
+
);
|
|
366
|
+
firmwareUpdatePlan = undefined;
|
|
367
|
+
}
|
|
318
368
|
|
|
319
369
|
return {
|
|
320
370
|
firmware: firmwareRelease,
|
|
@@ -329,7 +379,8 @@ export default class CheckAllFirmwareRelease extends BaseMethod {
|
|
|
329
379
|
}
|
|
330
380
|
: undefined,
|
|
331
381
|
features,
|
|
332
|
-
|
|
382
|
+
firmwareUpdatePlan,
|
|
383
|
+
} as unknown as AllFirmwareRelease;
|
|
333
384
|
}
|
|
334
385
|
|
|
335
386
|
private async runProtocolV2(): Promise<AllFirmwareRelease> {
|
|
@@ -10,6 +10,7 @@ import { UI_REQUEST } from '../constants/ui-request';
|
|
|
10
10
|
import { BaseMethod } from './BaseMethod';
|
|
11
11
|
import { validateParams } from './helpers/paramsValidator';
|
|
12
12
|
import { getBinary } from './firmware/getBinary';
|
|
13
|
+
import { BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS } from './firmware/FirmwareUpdateBaseMethod';
|
|
13
14
|
import { uploadFirmware } from './firmware/uploadFirmware';
|
|
14
15
|
import { createUiMessage } from '../events';
|
|
15
16
|
import { DeviceModelToTypes, type KnownDevice } from '../types';
|
|
@@ -78,6 +79,10 @@ export default class FirmwareUpdate extends BaseMethod<Params> {
|
|
|
78
79
|
this.checkPromise = createDeferred();
|
|
79
80
|
const env = DataManager.getSettings('env');
|
|
80
81
|
const isBleReconnect = connectId && DataManager.isBleConnect(env);
|
|
82
|
+
// Tracks the in-flight BLE probe so interval ticks never overlap: a superseded
|
|
83
|
+
// Initialize left pending on the V1 transport is exactly what later times out
|
|
84
|
+
// and used to tear down the connection mid-firmware-upload.
|
|
85
|
+
let bleProbeInFlight = false;
|
|
81
86
|
|
|
82
87
|
Log.log('FirmwareUpdate [checkDeviceToBootloader] isBleReconnect: ', isBleReconnect);
|
|
83
88
|
|
|
@@ -85,6 +90,8 @@ export default class FirmwareUpdate extends BaseMethod<Params> {
|
|
|
85
90
|
const intervalTimer: ReturnType<typeof setInterval> | undefined = setInterval(
|
|
86
91
|
async () => {
|
|
87
92
|
if (isBleReconnect) {
|
|
93
|
+
if (bleProbeInFlight) return;
|
|
94
|
+
bleProbeInFlight = true;
|
|
88
95
|
try {
|
|
89
96
|
await this.device.deviceConnector?.acquire(
|
|
90
97
|
this.device.originalDescriptor.id,
|
|
@@ -92,7 +99,10 @@ export default class FirmwareUpdate extends BaseMethod<Params> {
|
|
|
92
99
|
true,
|
|
93
100
|
this.payload.connectProtocol ?? this.device.originalDescriptor.protocolType
|
|
94
101
|
);
|
|
95
|
-
|
|
102
|
+
// Bound each probe so a request the rebooting device never received
|
|
103
|
+
// frees the slot for the next tick instead of hanging into the
|
|
104
|
+
// 30s reboot budget.
|
|
105
|
+
await this.device.initialize({ timeoutMs: BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS });
|
|
96
106
|
if (this.device.isBootloader()) {
|
|
97
107
|
clearInterval(intervalTimer);
|
|
98
108
|
this.checkPromise?.resolve(true);
|
|
@@ -100,6 +110,8 @@ export default class FirmwareUpdate extends BaseMethod<Params> {
|
|
|
100
110
|
} catch (e) {
|
|
101
111
|
// ignore error because of device is not connected
|
|
102
112
|
Log.log('catch Bluetooth error when device is restarting: ', e);
|
|
113
|
+
} finally {
|
|
114
|
+
bleProbeInFlight = false;
|
|
103
115
|
}
|
|
104
116
|
} else {
|
|
105
117
|
const deviceDiff = await this.device.deviceConnector?.enumerate();
|
|
@@ -14,18 +14,45 @@ import { BaseMethod } from './BaseMethod';
|
|
|
14
14
|
import { validateParams } from './helpers/paramsValidator';
|
|
15
15
|
import { DevicePool } from '../device/DevicePool';
|
|
16
16
|
import { getBinary, getInfo, getSysResourceBinary } from './firmware/getBinary';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
17
|
+
import { normalizeFirmwarePreparationError } from './firmware/FirmwarePreparationError';
|
|
18
|
+
import {
|
|
19
|
+
updateResources,
|
|
20
|
+
updateResourcesFromSources,
|
|
21
|
+
uploadFirmwareFromSource,
|
|
22
|
+
} from './firmware/uploadFirmware';
|
|
23
|
+
import { BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS } from './firmware/FirmwareUpdateBaseMethod';
|
|
24
|
+
import { LoggerNames, getDeviceType, getDeviceUUID, getLogger, wait } from '../utils';
|
|
25
|
+
import { resolveDeviceBootloaderMode } from '../utils/deviceFeaturesCompat';
|
|
19
26
|
import { FirmwareUpdateTipMessage, createUiMessage } from '../events/ui-request';
|
|
20
27
|
import { DeviceModelToTypes } from '../types';
|
|
21
28
|
import { DataManager } from '../data-manager';
|
|
22
29
|
import { DEVICE } from '../events';
|
|
30
|
+
import { type FirmwareByteSource, openFirmwareByteSource } from './firmware/FirmwareArtifactSource';
|
|
31
|
+
import { resolveFirmwareUpdateHostBinding } from './firmware/FirmwareHostBinding';
|
|
32
|
+
import {
|
|
33
|
+
assertFirmwareUpdatePreparedPlanBinding,
|
|
34
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity,
|
|
35
|
+
getFirmwareUpdateResourceName,
|
|
36
|
+
} from './firmware/FirmwareUpdatePreparedPlan';
|
|
23
37
|
|
|
24
38
|
import type { Features, KnownDevice } from '../types';
|
|
25
39
|
import type { FirmwareBinary } from './firmware/getBinary';
|
|
40
|
+
import type {
|
|
41
|
+
FirmwareArtifactReader,
|
|
42
|
+
FirmwareArtifactReference,
|
|
43
|
+
} from '../types/api/firmwareUpdate';
|
|
44
|
+
import type { FirmwareUpdatePreparedPlan } from '../types/api/firmwareUpdatePreparedPlan';
|
|
26
45
|
|
|
27
46
|
type Params = {
|
|
28
47
|
binary?: ArrayBuffer;
|
|
48
|
+
artifact?: FirmwareArtifactReference;
|
|
49
|
+
resourceEntries?: Array<{
|
|
50
|
+
entryName: string;
|
|
51
|
+
artifact: FirmwareArtifactReference;
|
|
52
|
+
}>;
|
|
53
|
+
artifactReader?: FirmwareArtifactReader;
|
|
54
|
+
preparedPlan?: FirmwareUpdatePreparedPlan;
|
|
55
|
+
platform?: FirmwareUpdatePreparedPlan['platform'];
|
|
29
56
|
version?: number[];
|
|
30
57
|
updateType: 'firmware' | 'ble';
|
|
31
58
|
forcedUpdateRes?: boolean;
|
|
@@ -106,6 +133,13 @@ const normalizeFirmwareBinary = (binary: unknown): FirmwareBinary | undefined =>
|
|
|
106
133
|
return normalized.buffer;
|
|
107
134
|
};
|
|
108
135
|
|
|
136
|
+
const toArrayBuffer = (binary: FirmwareBinary): ArrayBuffer => {
|
|
137
|
+
if (binary instanceof ArrayBuffer) {
|
|
138
|
+
return binary;
|
|
139
|
+
}
|
|
140
|
+
return new Uint8Array(binary.buffer, binary.byteOffset, binary.byteLength).slice().buffer;
|
|
141
|
+
};
|
|
142
|
+
|
|
109
143
|
export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
110
144
|
checkPromise: Deferred<any> | null = null;
|
|
111
145
|
|
|
@@ -152,6 +186,32 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
|
152
186
|
binary: payload.binary,
|
|
153
187
|
};
|
|
154
188
|
}
|
|
189
|
+
|
|
190
|
+
if ('artifact' in payload) {
|
|
191
|
+
const hostBinding = resolveFirmwareUpdateHostBinding(payload.hostBindingGeneration);
|
|
192
|
+
const target = payload.isUpdateBootloader ? 'bootloader' : payload.updateType;
|
|
193
|
+
const resourceBindings = (payload.resourceEntries ?? []).map(
|
|
194
|
+
(entry: { entryName: string; artifact: FirmwareArtifactReference }) => ({
|
|
195
|
+
target: 'resource' as const,
|
|
196
|
+
entryName: entry.entryName,
|
|
197
|
+
artifact: entry.artifact,
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
assertFirmwareUpdatePreparedPlanBinding({
|
|
201
|
+
preparedPlan: payload.preparedPlan,
|
|
202
|
+
executor: 'v2',
|
|
203
|
+
platform: payload.platform,
|
|
204
|
+
scopeTargets: [target, ...(target === 'firmware' ? (['resource'] as const) : [])],
|
|
205
|
+
bindings: [{ target, artifact: payload.artifact }, ...resourceBindings],
|
|
206
|
+
});
|
|
207
|
+
this.params = {
|
|
208
|
+
...this.params,
|
|
209
|
+
preparedPlan: payload.preparedPlan,
|
|
210
|
+
artifact: payload.artifact,
|
|
211
|
+
resourceEntries: payload.resourceEntries,
|
|
212
|
+
artifactReader: hostBinding.artifactReader,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
155
215
|
}
|
|
156
216
|
|
|
157
217
|
postTipMessage = (message: string) => {
|
|
@@ -187,6 +247,10 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
|
187
247
|
this.checkPromise = createDeferred();
|
|
188
248
|
const env = DataManager.getSettings('env');
|
|
189
249
|
const isBleReconnect = connectId && DataManager.isBleConnect(env);
|
|
250
|
+
// Tracks the in-flight BLE probe so interval ticks never overlap: a superseded
|
|
251
|
+
// Initialize left pending on the V1 transport is exactly what later times out
|
|
252
|
+
// and used to tear down the connection mid-firmware-upload.
|
|
253
|
+
let bleProbeInFlight = false;
|
|
190
254
|
|
|
191
255
|
Log.log('FirmwareUpdateV2 [checkDeviceToBootloader] isBleReconnect: ', isBleReconnect);
|
|
192
256
|
|
|
@@ -234,6 +298,8 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
|
234
298
|
}
|
|
235
299
|
|
|
236
300
|
if (isBleReconnect) {
|
|
301
|
+
if (bleProbeInFlight) return;
|
|
302
|
+
bleProbeInFlight = true;
|
|
237
303
|
try {
|
|
238
304
|
await this.device.deviceConnector?.acquire(
|
|
239
305
|
this.device.originalDescriptor.id,
|
|
@@ -241,7 +307,10 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
|
241
307
|
true,
|
|
242
308
|
this.payload.connectProtocol ?? this.device.originalDescriptor.protocolType
|
|
243
309
|
);
|
|
244
|
-
|
|
310
|
+
// Bound each probe so a request the rebooting device never received
|
|
311
|
+
// frees the slot for the next tick instead of hanging into the
|
|
312
|
+
// 30s reboot budget.
|
|
313
|
+
await this.device.initialize({ timeoutMs: BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS });
|
|
245
314
|
if (this.device.isBootloader()) {
|
|
246
315
|
clearInterval(intervalTimer);
|
|
247
316
|
this.checkPromise?.resolve(true);
|
|
@@ -249,6 +318,8 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
|
249
318
|
} catch (e) {
|
|
250
319
|
// ignore error because of device is not connected
|
|
251
320
|
Log.log('catch Bluetooth error when device is restarting: ', e);
|
|
321
|
+
} finally {
|
|
322
|
+
bleProbeInFlight = false;
|
|
252
323
|
}
|
|
253
324
|
} else {
|
|
254
325
|
await this._checkDeviceInBootloaderMode(connectId, intervalTimer, timeoutTimer);
|
|
@@ -344,151 +415,255 @@ export default class FirmwareUpdateV2 extends BaseMethod<Params> {
|
|
|
344
415
|
const { features, commands } = device;
|
|
345
416
|
const deviceType = device.getCurrentDeviceType();
|
|
346
417
|
|
|
418
|
+
// Protocol V2 (Pro2) uses DeviceFirmwareUpdate and must not enter this legacy flow.
|
|
419
|
+
if (device.isProtocolV2()) {
|
|
420
|
+
throw ERRORS.TypedError(
|
|
421
|
+
HardwareErrorCode.RuntimeError,
|
|
422
|
+
'Protocol V2 firmware update must use firmwareUpdateV4'
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
if (params.preparedPlan) {
|
|
426
|
+
assertFirmwareUpdatePreparedPlanDeviceIdentity({
|
|
427
|
+
preparedPlan: params.preparedPlan,
|
|
428
|
+
deviceIdentity: getDeviceUUID(features) || undefined,
|
|
429
|
+
bootloaderMode: resolveDeviceBootloaderMode(features),
|
|
430
|
+
deviceModel: String(getDeviceType(features)),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
347
434
|
const deviceFirmwareType = device.getCurrentFirmwareType();
|
|
348
435
|
const firmwareType = params.firmwareType ?? deviceFirmwareType;
|
|
349
436
|
|
|
350
437
|
this.checkVersionForCopyTouchResource(features, firmwareType);
|
|
351
438
|
|
|
352
|
-
let
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (params.binary !== undefined) {
|
|
360
|
-
preparedBinary = normalizeFirmwareBinary(params.binary);
|
|
361
|
-
if (!preparedBinary) {
|
|
362
|
-
throw new Error('firmware binary is empty or invalid');
|
|
439
|
+
let preparedSource: FirmwareByteSource | undefined;
|
|
440
|
+
try {
|
|
441
|
+
const acquireFirmwareSource = async (): Promise<FirmwareByteSource> => {
|
|
442
|
+
try {
|
|
443
|
+
if (preparedSource) {
|
|
444
|
+
return preparedSource;
|
|
363
445
|
}
|
|
364
|
-
return preparedBinary;
|
|
365
|
-
}
|
|
366
446
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
requestOptions: FIRMWARE_DOWNLOAD_REQUEST_OPTIONS,
|
|
382
|
-
});
|
|
383
|
-
preparedBinary = normalizeFirmwareBinary(firmware.binary);
|
|
384
|
-
if (!preparedBinary) {
|
|
385
|
-
throw new Error('downloaded firmware binary is empty or invalid');
|
|
386
|
-
}
|
|
387
|
-
this.postTipMessage('DownloadFirmwareSuccess');
|
|
388
|
-
return preparedBinary;
|
|
389
|
-
} catch (err) {
|
|
390
|
-
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
391
|
-
}
|
|
392
|
-
};
|
|
447
|
+
if (params.binary !== undefined) {
|
|
448
|
+
const binary = normalizeFirmwareBinary(params.binary);
|
|
449
|
+
if (!binary) {
|
|
450
|
+
throw new Error('firmware binary is empty or invalid');
|
|
451
|
+
}
|
|
452
|
+
const source = await openFirmwareByteSource({
|
|
453
|
+
binary: toArrayBuffer(binary),
|
|
454
|
+
});
|
|
455
|
+
if (!source) {
|
|
456
|
+
throw new Error('firmware binary is empty or invalid');
|
|
457
|
+
}
|
|
458
|
+
preparedSource = source;
|
|
459
|
+
return source;
|
|
460
|
+
}
|
|
393
461
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
462
|
+
if (params.artifact) {
|
|
463
|
+
const source = await openFirmwareByteSource({
|
|
464
|
+
artifact: params.artifact,
|
|
465
|
+
reader: params.artifactReader,
|
|
466
|
+
});
|
|
467
|
+
if (!source) {
|
|
468
|
+
throw new Error('firmware artifact is not prepared');
|
|
469
|
+
}
|
|
470
|
+
preparedSource = source;
|
|
471
|
+
return source;
|
|
472
|
+
}
|
|
400
473
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
features,
|
|
406
|
-
forcedUpdateRes: params.forcedUpdateRes,
|
|
407
|
-
firmwareType,
|
|
408
|
-
});
|
|
409
|
-
if (resourceUrl) {
|
|
410
|
-
this.postTipMessage('DownloadLatestUiResource');
|
|
411
|
-
const resource = await getSysResourceBinary(resourceUrl);
|
|
412
|
-
this.postTipMessage('DownloadLatestUiResourceSuccess');
|
|
413
|
-
if (resource) {
|
|
414
|
-
await updateResources(
|
|
415
|
-
this.device.getCommands().typedCall.bind(this.device.getCommands()),
|
|
416
|
-
this.postMessage,
|
|
417
|
-
device,
|
|
418
|
-
resource.binary
|
|
474
|
+
if (!device.features) {
|
|
475
|
+
throw ERRORS.TypedError(
|
|
476
|
+
HardwareErrorCode.RuntimeError,
|
|
477
|
+
'no features found for this device'
|
|
419
478
|
);
|
|
420
479
|
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// check if the device commands has been disposed
|
|
425
|
-
this.device?.commands?.checkDisposed();
|
|
426
480
|
|
|
427
|
-
|
|
428
|
-
|
|
481
|
+
if (
|
|
482
|
+
params.artifactReader ||
|
|
483
|
+
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
484
|
+
) {
|
|
485
|
+
throw ERRORS.TypedError(
|
|
486
|
+
HardwareErrorCode.RuntimeError,
|
|
487
|
+
'Firmware must be prepared by the external firmware host',
|
|
488
|
+
{
|
|
489
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
490
|
+
artifactName: 'firmware',
|
|
491
|
+
}
|
|
492
|
+
);
|
|
493
|
+
}
|
|
429
494
|
|
|
430
|
-
|
|
431
|
-
|
|
495
|
+
this.postTipMessage('DownloadFirmware');
|
|
496
|
+
const firmware = await getBinary({
|
|
497
|
+
features: device.features,
|
|
498
|
+
version: params.version,
|
|
499
|
+
updateType: params.updateType,
|
|
500
|
+
isUpdateBootloader: params.isUpdateBootloader,
|
|
501
|
+
firmwareType,
|
|
502
|
+
requestOptions: FIRMWARE_DOWNLOAD_REQUEST_OPTIONS,
|
|
503
|
+
});
|
|
504
|
+
const binary = normalizeFirmwareBinary(firmware.binary);
|
|
505
|
+
if (!binary) {
|
|
506
|
+
throw new Error('downloaded firmware binary is empty or invalid');
|
|
507
|
+
}
|
|
508
|
+
const source = await openFirmwareByteSource({
|
|
509
|
+
binary: toArrayBuffer(binary),
|
|
510
|
+
});
|
|
511
|
+
if (!source) {
|
|
512
|
+
throw new Error('downloaded firmware binary is empty or invalid');
|
|
513
|
+
}
|
|
514
|
+
preparedSource = source;
|
|
515
|
+
this.postTipMessage('DownloadFirmwareSuccess');
|
|
516
|
+
return source;
|
|
517
|
+
} catch (err) {
|
|
518
|
+
throw normalizeFirmwarePreparationError(err);
|
|
519
|
+
}
|
|
520
|
+
};
|
|
432
521
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (bootRes.type === 'CallMethodError') {
|
|
439
|
-
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure);
|
|
522
|
+
if (!device.isBootloader() && features) {
|
|
523
|
+
const serialNo = device.getCurrentSerialNo();
|
|
524
|
+
// should go to bootloader mode manually
|
|
525
|
+
if (this.isEnteredManuallyBoot()) {
|
|
526
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateManuallyEnterBoot);
|
|
440
527
|
}
|
|
441
|
-
this.postTipMessage('GoToBootloaderSuccess');
|
|
442
|
-
this.checkDeviceToBootloader(this.payload.connectId);
|
|
443
528
|
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
529
|
+
// All network-backed input must be ready before the first device mutation.
|
|
530
|
+
await acquireFirmwareSource();
|
|
531
|
+
|
|
532
|
+
// check & upgrade firmware resource
|
|
533
|
+
if (this.isSupportResourceUpdate(params.updateType)) {
|
|
534
|
+
this.postTipMessage('CheckLatestUiResource');
|
|
535
|
+
const resourceUrl = DataManager.getSysResourcesLatestRelease({
|
|
536
|
+
features,
|
|
537
|
+
forcedUpdateRes: params.forcedUpdateRes,
|
|
538
|
+
firmwareType,
|
|
539
|
+
});
|
|
540
|
+
if (resourceUrl) {
|
|
541
|
+
this.postTipMessage('DownloadLatestUiResource');
|
|
542
|
+
if (params.resourceEntries?.length) {
|
|
543
|
+
const sources: Array<{
|
|
544
|
+
entryName: string;
|
|
545
|
+
source: FirmwareByteSource;
|
|
546
|
+
}> = [];
|
|
547
|
+
try {
|
|
548
|
+
for (const entry of params.resourceEntries) {
|
|
549
|
+
const resourceName = getFirmwareUpdateResourceName(entry.entryName);
|
|
550
|
+
const source = await openFirmwareByteSource({
|
|
551
|
+
artifact: entry.artifact,
|
|
552
|
+
reader: params.artifactReader,
|
|
553
|
+
});
|
|
554
|
+
if (!source) {
|
|
555
|
+
throw new Error('Firmware resource entry is not prepared');
|
|
556
|
+
}
|
|
557
|
+
sources.push({ entryName: resourceName, source });
|
|
558
|
+
}
|
|
559
|
+
await updateResourcesFromSources(
|
|
560
|
+
this.device.getCommands().typedCall.bind(this.device.getCommands()),
|
|
561
|
+
this.postMessage,
|
|
562
|
+
device,
|
|
563
|
+
sources.map(entry => ({
|
|
564
|
+
entryName: entry.entryName,
|
|
565
|
+
source: entry.source,
|
|
566
|
+
}))
|
|
567
|
+
);
|
|
568
|
+
} finally {
|
|
569
|
+
await Promise.all(
|
|
570
|
+
sources.map(entry => entry.source.close().catch(() => undefined))
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
this.postTipMessage('DownloadLatestUiResourceSuccess');
|
|
574
|
+
} else {
|
|
575
|
+
if (
|
|
576
|
+
params.artifactReader ||
|
|
577
|
+
DataManager.getSettings('firmwareManifestMode') === 'external-only'
|
|
578
|
+
) {
|
|
579
|
+
throw ERRORS.TypedError(
|
|
580
|
+
HardwareErrorCode.RuntimeError,
|
|
581
|
+
'Firmware resource must be prepared by the external firmware host',
|
|
582
|
+
{
|
|
583
|
+
firmwareUpdateCode: 'FirmwareArtifactsNotPrepared',
|
|
584
|
+
artifactName: 'resource',
|
|
585
|
+
}
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
const resourceBinary: ArrayBuffer | Buffer = (await getSysResourceBinary(resourceUrl))
|
|
589
|
+
.binary;
|
|
590
|
+
this.postTipMessage('DownloadLatestUiResourceSuccess');
|
|
591
|
+
await updateResources(
|
|
592
|
+
this.device.getCommands().typedCall.bind(this.device.getCommands()),
|
|
593
|
+
this.postMessage,
|
|
594
|
+
device,
|
|
595
|
+
resourceBinary
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
447
599
|
}
|
|
448
|
-
delete DevicePool.devicesCache[''];
|
|
449
|
-
await this.checkPromise?.promise;
|
|
450
|
-
this.checkPromise = null;
|
|
451
600
|
|
|
452
|
-
//
|
|
601
|
+
// The request may outlive the current transport command instance.
|
|
453
602
|
this.device?.commands?.checkDisposed();
|
|
454
603
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
604
|
+
// auto go to bootloader mode
|
|
605
|
+
try {
|
|
606
|
+
this.postTipMessage('AutoRebootToBootloader');
|
|
607
|
+
const bootRes = await commands.typedCall('DeviceBackToBoot', 'Success');
|
|
608
|
+
// @ts-expect-error
|
|
609
|
+
if (bootRes.type === 'CallMethodError') {
|
|
610
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure);
|
|
611
|
+
}
|
|
612
|
+
this.postTipMessage('GoToBootloaderSuccess');
|
|
613
|
+
this.checkDeviceToBootloader(this.payload.connectId);
|
|
614
|
+
|
|
615
|
+
// force clean classic device cache so that the device can initialize again
|
|
616
|
+
if (DeviceModelToTypes.model_classic.includes(deviceType)) {
|
|
617
|
+
DevicePool.clearDeviceCache(serialNo);
|
|
618
|
+
}
|
|
619
|
+
delete DevicePool.devicesCache[''];
|
|
620
|
+
await this.checkPromise?.promise;
|
|
621
|
+
this.checkPromise = null;
|
|
622
|
+
|
|
623
|
+
// check if the device commands has been disposed
|
|
624
|
+
this.device?.commands?.checkDisposed();
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Touch 1 with bootloader v2.5.0 issue: BLE chip need more time for looking up name, here change the delay time to 3000ms after rebooting.
|
|
628
|
+
*/
|
|
629
|
+
const isTouch = DeviceModelToTypes.model_touch.includes(deviceType);
|
|
630
|
+
await wait(isTouch ? 3000 : 1500);
|
|
631
|
+
} catch (e) {
|
|
632
|
+
if (e instanceof HardwareError) {
|
|
633
|
+
return Promise.reject(e);
|
|
634
|
+
}
|
|
635
|
+
console.log('auto go to bootloader mode failed: ', e);
|
|
636
|
+
return Promise.reject(
|
|
637
|
+
ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure)
|
|
638
|
+
);
|
|
463
639
|
}
|
|
464
|
-
console.log('auto go to bootloader mode failed: ', e);
|
|
465
|
-
return Promise.reject(
|
|
466
|
-
ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure)
|
|
467
|
-
);
|
|
468
640
|
}
|
|
469
|
-
}
|
|
470
641
|
|
|
471
|
-
|
|
472
|
-
|
|
642
|
+
// Devices already in bootloader mode still acquire through the same helper.
|
|
643
|
+
const source = await acquireFirmwareSource();
|
|
473
644
|
|
|
474
|
-
|
|
475
|
-
|
|
645
|
+
// check if the device commands has been disposed
|
|
646
|
+
this.device?.commands?.checkDisposed();
|
|
476
647
|
|
|
477
|
-
|
|
648
|
+
await this.device.acquire();
|
|
478
649
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
650
|
+
const response = await uploadFirmwareFromSource(
|
|
651
|
+
params.updateType,
|
|
652
|
+
this.device.getCommands().typedCall.bind(this.device.getCommands()),
|
|
653
|
+
this.postMessage,
|
|
654
|
+
device,
|
|
655
|
+
source,
|
|
656
|
+
true,
|
|
657
|
+
params.isUpdateBootloader
|
|
658
|
+
);
|
|
487
659
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
660
|
+
if (this.connectId) {
|
|
661
|
+
DevicePool.clearDeviceCache(this.connectId);
|
|
662
|
+
}
|
|
491
663
|
|
|
492
|
-
|
|
664
|
+
return response;
|
|
665
|
+
} finally {
|
|
666
|
+
await preparedSource?.close().catch(() => undefined);
|
|
667
|
+
}
|
|
493
668
|
}
|
|
494
669
|
}
|