@onekeyfe/hd-core 1.2.0-alpha.58 → 1.2.0-alpha.59
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__/DeviceCommands.test.ts +5 -15
- package/__tests__/device-pool-state.test.ts +22 -0
- package/__tests__/device-state-mapper.test.ts +12 -0
- package/__tests__/device-utils.test.ts +48 -1
- package/__tests__/deviceSettings.test.ts +10 -0
- package/__tests__/deviceUploadNft.test.ts +4 -28
- package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
- package/__tests__/protocol-v2-resources.test.ts +28 -13
- package/__tests__/protocol-v2-unlock-policy.test.ts +34 -0
- package/__tests__/protocol-v2.test.ts +159 -137
- package/__tests__/tron-sign-message-init.test.ts +2 -2
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +4 -3
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/kaspa/KaspaGetAddress.d.ts +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts +1 -1
- package/dist/api/polkadot/PolkadotGetAddress.d.ts +1 -1
- package/dist/api/polkadot/networks.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/solana/SolSignMessage.d.ts +1 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts +1 -1
- package/dist/api/solana/SolSignTransaction.d.ts +2 -2
- package/dist/api/sui/SuiGetAddress.d.ts +1 -1
- package/dist/api/sui/SuiGetPublicKey.d.ts +1 -1
- package/dist/api/sui/SuiSignMessage.d.ts +1 -1
- package/dist/api/sui/SuiSignTransaction.d.ts +1 -1
- package/dist/api/ton/TonGetAddress.d.ts +1 -1
- package/dist/api/ton/TonSignMessage.d.ts +1 -1
- package/dist/api/ton/TonSignProof.d.ts +1 -1
- package/dist/api/tron/TronSignMessage.d.ts +2 -2
- package/dist/api/tron/TronSignTransaction.d.ts +1 -1
- package/dist/data-manager/DataManager.d.ts +7 -5
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -1
- package/dist/index.d.ts +44 -34
- package/dist/index.js +231 -168
- package/dist/protocols/protocol-v2/resources.d.ts +2 -2
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +3 -2
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/device.d.ts +2 -2
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/settings.d.ts +22 -14
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/deviceSettings.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +7 -5
- package/src/api/FirmwareUpdateV4.ts +115 -102
- package/src/api/OpenWalletSession.ts +1 -0
- package/src/api/kaspa/KaspaGetAddress.ts +1 -1
- package/src/api/kaspa/KaspaSignTransaction.ts +1 -1
- package/src/api/polkadot/networks.ts +3 -3
- package/src/api/protocol-v2/DeviceUploadNft.ts +7 -12
- package/src/api/protocol-v2/helpers.ts +0 -1
- package/src/api/solana/SolSignMessage.ts +1 -1
- package/src/api/solana/SolSignOffchainMessage.ts +1 -1
- package/src/api/solana/SolSignTransaction.ts +2 -2
- package/src/api/sui/SuiGetAddress.ts +1 -1
- package/src/api/sui/SuiGetPublicKey.ts +1 -1
- package/src/api/sui/SuiSignMessage.ts +1 -1
- package/src/api/sui/SuiSignTransaction.ts +1 -1
- package/src/api/ton/TonGetAddress.ts +1 -1
- package/src/api/ton/TonSignMessage.ts +1 -1
- package/src/api/ton/TonSignProof.ts +1 -1
- package/src/api/tron/TronSignMessage.ts +2 -2
- package/src/api/tron/TronSignTransaction.ts +1 -1
- package/src/data-manager/DataManager.ts +28 -9
- package/src/device/Device.ts +15 -6
- package/src/device/DeviceCommands.ts +13 -10
- package/src/device/DevicePool.ts +17 -8
- package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
- package/src/deviceProfile/protocolV2DeviceIdentity.ts +3 -0
- package/src/protocols/protocol-v2/resources.ts +44 -13
- package/src/types/api/checkAllFirmwareRelease.ts +1 -1
- package/src/types/api/firmwareUpdate.ts +5 -5
- package/src/types/device.ts +12 -3
- package/src/types/settings.ts +30 -21
- package/src/utils/deviceFeaturesCompat.ts +5 -0
- package/src/utils/deviceFeaturesUtils.ts +6 -3
- package/src/utils/deviceInfoUtils.ts +2 -0
- package/src/utils/deviceSettings.ts +3 -0
|
@@ -69,7 +69,7 @@ function getFirmwareTypeFromField(firmwareField: IFirmwareField): EFirmwareType
|
|
|
69
69
|
|
|
70
70
|
export default class DataManager {
|
|
71
71
|
static deviceMap: DeviceTypeMap & {
|
|
72
|
-
[k: string]: DeviceTypeMap[keyof DeviceTypeMap] | undefined;
|
|
72
|
+
[k: string]: NonNullable<DeviceTypeMap[keyof DeviceTypeMap]> | undefined;
|
|
73
73
|
} = {
|
|
74
74
|
[EDeviceType.Classic]: {
|
|
75
75
|
firmware: [],
|
|
@@ -95,6 +95,10 @@ export default class DataManager {
|
|
|
95
95
|
firmware: [],
|
|
96
96
|
ble: [],
|
|
97
97
|
},
|
|
98
|
+
[EDeviceType.Neo]: {
|
|
99
|
+
firmware: [],
|
|
100
|
+
ble: [],
|
|
101
|
+
},
|
|
98
102
|
[EDeviceType.ClassicPure]: {
|
|
99
103
|
firmware: [],
|
|
100
104
|
ble: [],
|
|
@@ -359,7 +363,7 @@ export default class DataManager {
|
|
|
359
363
|
|
|
360
364
|
private static enrichFirmwareReleaseInfo(
|
|
361
365
|
deviceData: DeviceTypeMap[keyof DeviceTypeMap] | undefined
|
|
362
|
-
): DeviceTypeMap[keyof DeviceTypeMap] {
|
|
366
|
+
): NonNullable<DeviceTypeMap[keyof DeviceTypeMap]> {
|
|
363
367
|
// Safety check: return default structure if input is undefined/null
|
|
364
368
|
if (!deviceData || typeof deviceData !== 'object') {
|
|
365
369
|
return {
|
|
@@ -451,10 +455,15 @@ export default class DataManager {
|
|
|
451
455
|
// 3. Apply config if available
|
|
452
456
|
if (data) {
|
|
453
457
|
let pro2Resources: IProtocolV2Resources | undefined;
|
|
458
|
+
let neoResources: IProtocolV2Resources | undefined;
|
|
459
|
+
this.protocolV2ResourcesConfigError = undefined;
|
|
454
460
|
try {
|
|
455
461
|
pro2Resources = parseProtocolV2Resources(
|
|
456
462
|
(data.pro2 as { resources?: unknown } | undefined)?.resources
|
|
457
463
|
);
|
|
464
|
+
neoResources = parseProtocolV2Resources(
|
|
465
|
+
(data.neo as { resources?: unknown } | undefined)?.resources
|
|
466
|
+
);
|
|
458
467
|
} catch (error) {
|
|
459
468
|
// Firmware resource metadata is not required for base communication. If the
|
|
460
469
|
// remote config is temporarily incomplete, disable this resource update only.
|
|
@@ -463,7 +472,9 @@ export default class DataManager {
|
|
|
463
472
|
Log.warn('[DataConfig] Ignoring invalid Pro2 resources config:', error);
|
|
464
473
|
}
|
|
465
474
|
const enrichedPro2Config = this.enrichFirmwareReleaseInfo(data.pro2);
|
|
475
|
+
const enrichedNeoConfig = this.enrichFirmwareReleaseInfo(data.neo);
|
|
466
476
|
const { resources: _unvalidatedResources, ...pro2Config } = enrichedPro2Config;
|
|
477
|
+
const { resources: _unvalidatedNeoResources, ...neoConfig } = enrichedNeoConfig;
|
|
467
478
|
Log.log(`[DataConfig] Config loaded successfully via [${fetchMethod}]`);
|
|
468
479
|
this.deviceMap = {
|
|
469
480
|
[EDeviceType.Classic]: this.enrichFirmwareReleaseInfo(data.classic),
|
|
@@ -476,6 +487,10 @@ export default class DataManager {
|
|
|
476
487
|
...pro2Config,
|
|
477
488
|
...(pro2Resources ? { resources: pro2Resources } : undefined),
|
|
478
489
|
},
|
|
490
|
+
[EDeviceType.Neo]: {
|
|
491
|
+
...neoConfig,
|
|
492
|
+
...(neoResources ? { resources: neoResources } : undefined),
|
|
493
|
+
},
|
|
479
494
|
};
|
|
480
495
|
this.assets = {
|
|
481
496
|
bridge: data.bridge,
|
|
@@ -509,7 +524,9 @@ export default class DataManager {
|
|
|
509
524
|
}
|
|
510
525
|
|
|
511
526
|
/** Force a fresh remote config before an update is allowed to mutate the device. */
|
|
512
|
-
static async forceReloadData(
|
|
527
|
+
static async forceReloadData({
|
|
528
|
+
requireResources = false,
|
|
529
|
+
}: { requireResources?: boolean } = {}): Promise<void> {
|
|
513
530
|
if (!this.settings) {
|
|
514
531
|
throw new Error('Remote config settings are not initialized');
|
|
515
532
|
}
|
|
@@ -520,21 +537,23 @@ export default class DataManager {
|
|
|
520
537
|
'Unable to refresh the latest remote config'
|
|
521
538
|
);
|
|
522
539
|
}
|
|
523
|
-
if (this.protocolV2ResourcesConfigError) {
|
|
540
|
+
if (requireResources && this.protocolV2ResourcesConfigError) {
|
|
524
541
|
throw ERRORS.TypedError(
|
|
525
|
-
HardwareErrorCode.
|
|
542
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
526
543
|
`Invalid Pro2 resources config: ${this.protocolV2ResourcesConfigError.message}`
|
|
527
544
|
);
|
|
528
545
|
}
|
|
529
546
|
this.lastCheckTimestamp = getTimeStamp();
|
|
530
547
|
}
|
|
531
548
|
|
|
532
|
-
static getProtocolV2Resources() {
|
|
533
|
-
return this.deviceMap[
|
|
549
|
+
static getProtocolV2Resources(deviceType: EDeviceType.Pro2 | EDeviceType.Neo = EDeviceType.Pro2) {
|
|
550
|
+
return this.deviceMap[deviceType]?.resources?.stable;
|
|
534
551
|
}
|
|
535
552
|
|
|
536
|
-
static getProtocolV2BootResources(
|
|
537
|
-
|
|
553
|
+
static getProtocolV2BootResources(
|
|
554
|
+
deviceType: EDeviceType.Pro2 | EDeviceType.Neo = EDeviceType.Pro2
|
|
555
|
+
) {
|
|
556
|
+
return this.deviceMap[deviceType]?.resources?.boot;
|
|
538
557
|
}
|
|
539
558
|
|
|
540
559
|
static getProtobufMessages(schema: ProtobufMessageSchema = 'v1CurrentSchema'): JSON {
|
package/src/device/Device.ts
CHANGED
|
@@ -696,6 +696,7 @@ export class Device extends EventEmitter {
|
|
|
696
696
|
// Most-specific model first; must match getMethodVersionRange in deviceInfoUtils,
|
|
697
697
|
// otherwise e.g. Classic1s resolves the looser model_mini range before model_classic1s.
|
|
698
698
|
const modelFallbacks: IDeviceModel[] = [
|
|
699
|
+
'model_pro2',
|
|
699
700
|
'model_classic1s',
|
|
700
701
|
'model_classic',
|
|
701
702
|
'model_mini',
|
|
@@ -715,7 +716,8 @@ export class Device extends EventEmitter {
|
|
|
715
716
|
if (
|
|
716
717
|
deviceType === EDeviceType.Touch ||
|
|
717
718
|
deviceType === EDeviceType.Pro ||
|
|
718
|
-
deviceType === EDeviceType.Pro2
|
|
719
|
+
deviceType === EDeviceType.Pro2 ||
|
|
720
|
+
deviceType === EDeviceType.Neo
|
|
719
721
|
) {
|
|
720
722
|
return { support: true };
|
|
721
723
|
}
|
|
@@ -732,7 +734,8 @@ export class Device extends EventEmitter {
|
|
|
732
734
|
if (
|
|
733
735
|
deviceType === EDeviceType.Touch ||
|
|
734
736
|
deviceType === EDeviceType.Pro ||
|
|
735
|
-
deviceType === EDeviceType.Pro2
|
|
737
|
+
deviceType === EDeviceType.Pro2 ||
|
|
738
|
+
deviceType === EDeviceType.Neo
|
|
736
739
|
) {
|
|
737
740
|
return { support: false };
|
|
738
741
|
}
|
|
@@ -1167,10 +1170,14 @@ export class Device extends EventEmitter {
|
|
|
1167
1170
|
const protocolV2DeviceType = runtimeDeviceInfo
|
|
1168
1171
|
? resolveProtocolV2DeviceIdentity(runtimeDeviceInfo.hw?.Device_type).deviceType
|
|
1169
1172
|
: this.getCurrentDeviceType();
|
|
1170
|
-
if (
|
|
1173
|
+
if (
|
|
1174
|
+
runtimeMode === 'romloader' &&
|
|
1175
|
+
protocolV2DeviceType !== EDeviceType.Pro2 &&
|
|
1176
|
+
protocolV2DeviceType !== EDeviceType.Neo
|
|
1177
|
+
) {
|
|
1171
1178
|
throw ERRORS.TypedError(
|
|
1172
1179
|
HardwareErrorCode.DeviceInitializeFailed,
|
|
1173
|
-
'Protocol V2 romloader mode is only supported for Pro2.'
|
|
1180
|
+
'Protocol V2 romloader mode is only supported for Pro2 and Neo.'
|
|
1174
1181
|
);
|
|
1175
1182
|
}
|
|
1176
1183
|
const deviceStatusSupported = supportsProtocolV2Message(
|
|
@@ -1569,7 +1576,8 @@ export class Device extends EventEmitter {
|
|
|
1569
1576
|
if (!this.state) return undefined;
|
|
1570
1577
|
return (
|
|
1571
1578
|
this.isProtocolV2() &&
|
|
1572
|
-
this.getCurrentDeviceType() === EDeviceType.Pro2
|
|
1579
|
+
(this.getCurrentDeviceType() === EDeviceType.Pro2 ||
|
|
1580
|
+
this.getCurrentDeviceType() === EDeviceType.Neo) &&
|
|
1573
1581
|
this.state.status.mode === 'romloader'
|
|
1574
1582
|
);
|
|
1575
1583
|
}
|
|
@@ -1615,7 +1623,8 @@ export class Device extends EventEmitter {
|
|
|
1615
1623
|
const isModeT =
|
|
1616
1624
|
deviceType === EDeviceType.Touch ||
|
|
1617
1625
|
deviceType === EDeviceType.Pro ||
|
|
1618
|
-
deviceType === EDeviceType.Pro2
|
|
1626
|
+
deviceType === EDeviceType.Pro2 ||
|
|
1627
|
+
deviceType === EDeviceType.Neo;
|
|
1619
1628
|
const unlocked = this.state?.status.unlocked;
|
|
1620
1629
|
const preCheckTouch = isModeT && unlocked === false;
|
|
1621
1630
|
const passphraseProtection = this.getCurrentPassphraseProtection();
|
|
@@ -54,8 +54,16 @@ const DEVICE_SESSION_CALLS = new Set([
|
|
|
54
54
|
'DeviceSessionAskPin',
|
|
55
55
|
'DeviceSessionAskPassphrase',
|
|
56
56
|
]);
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const DEVICE_CONTROL_CALLS = new Set([
|
|
58
|
+
'DeviceReboot',
|
|
59
|
+
'DeviceSettingsGet',
|
|
60
|
+
'DeviceSettingsSet',
|
|
61
|
+
'DeviceSettingsPageShow',
|
|
62
|
+
'DeviceCertificateWrite',
|
|
63
|
+
'DeviceCertificateRead',
|
|
64
|
+
'DeviceCertificateSign',
|
|
65
|
+
'DeviceMiscUsbMscControl',
|
|
66
|
+
]);
|
|
59
67
|
|
|
60
68
|
// Older Protocol V2 firmware may omit the cancellation subcode, so retain a
|
|
61
69
|
// narrow message fallback for compatibility.
|
|
@@ -501,14 +509,9 @@ export class DeviceCommands {
|
|
|
501
509
|
const normalizedMessage = message?.trim() ?? '';
|
|
502
510
|
const isProtocolV2ActionCancelledFailure =
|
|
503
511
|
this.device.isProtocolV2() && isProtocolV2ActionCancelledMessage(normalizedMessage);
|
|
504
|
-
const isProtocolV2ActionCancelledSubcode =
|
|
505
|
-
this.device.isProtocolV2() && subcode === PROTOCOL_V2_ACTION_CANCELLED_SUBCODE;
|
|
506
|
-
// DeviceErrorCode currently reuses subcode 1 for Busy. Resolve that
|
|
507
|
-
// domain-specific exception before applying the common cancellation map.
|
|
508
512
|
const isProtocolV2DeviceBusyFailure =
|
|
509
513
|
this.device.isProtocolV2() &&
|
|
510
|
-
|
|
511
|
-
!DEVICE_SESSION_CALLS.has(callType) &&
|
|
514
|
+
DEVICE_CONTROL_CALLS.has(callType) &&
|
|
512
515
|
subcode === DeviceErrorCode.DeviceError_Busy;
|
|
513
516
|
const isLegacyProtocolV2LockedFailure =
|
|
514
517
|
this.device.isProtocolV2() && /^device (?:is )?locked$/i.test(normalizedMessage);
|
|
@@ -574,8 +577,8 @@ export class DeviceCommands {
|
|
|
574
577
|
firmwareMessage: message,
|
|
575
578
|
});
|
|
576
579
|
} else if (
|
|
577
|
-
|
|
578
|
-
|
|
580
|
+
(DEVICE_CONTROL_CALLS.has(callType) &&
|
|
581
|
+
subcode === DeviceErrorCode.DeviceError_ActionCancelled) ||
|
|
579
582
|
isProtocolV2ActionCancelledFailure
|
|
580
583
|
) {
|
|
581
584
|
error = ERRORS.TypedError(HardwareErrorCode.ActionCancelled, message, {
|
package/src/device/DevicePool.ts
CHANGED
|
@@ -103,7 +103,7 @@ export class DevicePool extends EventEmitter {
|
|
|
103
103
|
const device = this.devicesCache[connectId];
|
|
104
104
|
if (device) {
|
|
105
105
|
const exist = descriptorList.find(d => d.path === device.originalDescriptor.path);
|
|
106
|
-
if (exist) {
|
|
106
|
+
if (exist && !initOptions?.forceProtocolDetection) {
|
|
107
107
|
// Log.debug('find existed Device: ', connectId);
|
|
108
108
|
device.updateDescriptor(exist, true);
|
|
109
109
|
await this._refreshRuntimeState(device, initOptions);
|
|
@@ -151,14 +151,23 @@ export class DevicePool extends EventEmitter {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
static async _createDevice(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
static async _createDevice(
|
|
155
|
+
descriptor: DeviceDescriptor,
|
|
156
|
+
initOptions?: InitOptions
|
|
157
|
+
): Promise<Device> {
|
|
158
|
+
const cachedDevice = this.getDeviceByPath(descriptor.path);
|
|
159
|
+
const forceProtocolDetection = initOptions?.forceProtocolDetection === true;
|
|
160
|
+
const isNewDevice = !cachedDevice;
|
|
161
|
+
const device = cachedDevice ?? Device.fromDescriptor(descriptor);
|
|
162
|
+
if (isNewDevice || forceProtocolDetection) {
|
|
158
163
|
device.deviceConnector = this.connector;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
164
|
+
if (forceProtocolDetection && !isNewDevice) {
|
|
165
|
+
await device.acquire(initOptions?.connectProtocol, { forceProtocolDetection: true });
|
|
166
|
+
} else {
|
|
167
|
+
await device.connect(initOptions?.connectProtocol, {
|
|
168
|
+
forceProtocolDetection: initOptions?.forceProtocolDetection,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
162
171
|
try {
|
|
163
172
|
await device.initialize(initOptions);
|
|
164
173
|
if (initOptions?.refreshRuntimeState && device.isProtocolV2()) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
resolveDeviceBleFirmwareVersion,
|
|
@@ -386,7 +386,7 @@ export const buildProtocolV2FeaturesPayload = ({
|
|
|
386
386
|
device_id: deviceId ?? undefined,
|
|
387
387
|
session_id: cached?.sessionId ?? undefined,
|
|
388
388
|
ble_name: bleName ?? undefined,
|
|
389
|
-
onekey_device_type:
|
|
389
|
+
onekey_device_type: deviceType === EDeviceType.Unknown ? undefined : deviceType.toUpperCase(),
|
|
390
390
|
passphrase_protection: passphraseProtection ?? undefined,
|
|
391
391
|
bootloader_mode: bootloaderMode,
|
|
392
392
|
passphraseState: cached?.passphraseState,
|
|
@@ -26,6 +26,9 @@ export const resolveProtocolV2DeviceIdentity = (
|
|
|
26
26
|
case DeviceType.PRO2:
|
|
27
27
|
case 'PRO2':
|
|
28
28
|
return { deviceType: EDeviceType.Pro2, model: 'pro2' };
|
|
29
|
+
case DeviceType.NEO:
|
|
30
|
+
case 'NEO':
|
|
31
|
+
return { deviceType: EDeviceType.Neo, model: 'neo' };
|
|
29
32
|
default:
|
|
30
33
|
return { deviceType: EDeviceType.Unknown, model: null };
|
|
31
34
|
}
|
|
@@ -4,6 +4,7 @@ import { PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE } from '@onekeyfe/hd-transport';
|
|
|
4
4
|
import type {
|
|
5
5
|
IProtocolV2BootResources,
|
|
6
6
|
IProtocolV2Resource,
|
|
7
|
+
IProtocolV2ResourceFile,
|
|
7
8
|
IProtocolV2ResourceType,
|
|
8
9
|
IProtocolV2Resources,
|
|
9
10
|
} from '../../types';
|
|
@@ -247,23 +248,53 @@ function validateBootResources(value: unknown): IProtocolV2BootResources {
|
|
|
247
248
|
if (resource.required !== false) {
|
|
248
249
|
throw new Error('Invalid Pro2 boot resources required flag: expected false');
|
|
249
250
|
}
|
|
250
|
-
if (resource.target !== '
|
|
251
|
-
throw new Error('Invalid Pro2 boot resources target: expected
|
|
251
|
+
if (resource.target !== 'RES') {
|
|
252
|
+
throw new Error('Invalid Pro2 boot resources target: expected RES');
|
|
252
253
|
}
|
|
253
|
-
if (
|
|
254
|
-
|
|
254
|
+
if (
|
|
255
|
+
resource.manifestUrl !== undefined &&
|
|
256
|
+
(typeof resource.manifestUrl !== 'string' || !resource.manifestUrl.startsWith('https://'))
|
|
257
|
+
) {
|
|
258
|
+
throw new Error('Invalid Pro2 boot resources manifestUrl');
|
|
255
259
|
}
|
|
256
|
-
if (!
|
|
257
|
-
throw new Error('Invalid Pro2 boot resources
|
|
260
|
+
if (!Array.isArray(resource.files) || resource.files.length === 0) {
|
|
261
|
+
throw new Error('Invalid Pro2 boot resources files');
|
|
262
|
+
}
|
|
263
|
+
const files = resource.files.map((value, index): IProtocolV2ResourceFile => {
|
|
264
|
+
if (!value || typeof value !== 'object') {
|
|
265
|
+
throw new Error(`Invalid Pro2 boot resource file at files[${index}]`);
|
|
266
|
+
}
|
|
267
|
+
const file = value as Partial<IProtocolV2ResourceFile>;
|
|
268
|
+
if (typeof file.url !== 'string' || !file.url.startsWith('https://')) {
|
|
269
|
+
throw new Error(`Invalid Pro2 boot resource url at files[${index}]`);
|
|
270
|
+
}
|
|
271
|
+
if (
|
|
272
|
+
typeof file.devicePath !== 'string' ||
|
|
273
|
+
!file.devicePath.startsWith('vol0:/') ||
|
|
274
|
+
file.devicePath.includes('..') ||
|
|
275
|
+
file.devicePath.includes('\\\\')
|
|
276
|
+
) {
|
|
277
|
+
throw new Error(`Invalid Pro2 boot resource devicePath at files[${index}]`);
|
|
278
|
+
}
|
|
279
|
+
if (!Number.isSafeInteger(file.size) || Number(file.size) <= 0) {
|
|
280
|
+
throw new Error(`Invalid Pro2 boot resource size at files[${index}]`);
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
...(typeof file.name === 'string' && file.name ? { name: file.name } : undefined),
|
|
284
|
+
url: file.url,
|
|
285
|
+
devicePath: file.devicePath,
|
|
286
|
+
size: Number(file.size),
|
|
287
|
+
fileHash: normalizeHex(file.fileHash, SHA256_HEX_LENGTH, `boot files[${index}].fileHash`),
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
if (new Set(files.map(file => file.devicePath)).size !== files.length) {
|
|
291
|
+
throw new Error('Invalid Pro2 boot resources files: duplicate devicePath');
|
|
258
292
|
}
|
|
259
293
|
return {
|
|
260
294
|
required: false,
|
|
261
|
-
target: '
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
fileHash: normalizeHex(resource.fileHash, SHA256_HEX_LENGTH, 'boot fileHash'),
|
|
265
|
-
payloadHash: normalizeHex(resource.payloadHash, SHA3_512_HEX_LENGTH, 'boot payloadHash'),
|
|
266
|
-
headerHash: normalizeHex(resource.headerHash, SHA3_512_HEX_LENGTH, 'boot headerHash'),
|
|
295
|
+
target: 'RES',
|
|
296
|
+
...(resource.manifestUrl ? { manifestUrl: resource.manifestUrl } : undefined),
|
|
297
|
+
files,
|
|
267
298
|
};
|
|
268
299
|
}
|
|
269
300
|
|
|
@@ -352,7 +383,7 @@ function bytesToHex(bytes: Uint8Array): string {
|
|
|
352
383
|
/** Verify the complete downloaded file before any device mutation. */
|
|
353
384
|
export function isProtocolV2ResourceFileValid(
|
|
354
385
|
binary: ArrayBuffer,
|
|
355
|
-
resource: Pick<
|
|
386
|
+
resource: Pick<IProtocolV2ResourceFile, 'size' | 'fileHash'>
|
|
356
387
|
): boolean {
|
|
357
388
|
if (binary.byteLength !== resource.size) return false;
|
|
358
389
|
return bytesToHex(sha256(new Uint8Array(binary))) === resource.fileHash.toLowerCase();
|
|
@@ -54,7 +54,7 @@ export type AllFirmwareRelease = {
|
|
|
54
54
|
bridge?: FirmwareRelease;
|
|
55
55
|
features?: Features;
|
|
56
56
|
protocol?: 'V1' | 'V2';
|
|
57
|
-
deviceType?: 'pro2';
|
|
57
|
+
deviceType?: 'pro2' | 'neo';
|
|
58
58
|
firmwareType?: EFirmwareType;
|
|
59
59
|
status?: ProtocolV2FirmwareReleaseStatus;
|
|
60
60
|
hasUpgrade?: boolean;
|
|
@@ -83,8 +83,6 @@ export interface FirmwareUpdateV4Params {
|
|
|
83
83
|
romloaderBinary?: ArrayBuffer;
|
|
84
84
|
/** FW_MGMT_TARGET_BOOTLOADER = 3 */
|
|
85
85
|
bootloaderBinary?: ArrayBuffer;
|
|
86
|
-
/** FW_MGMT_TARGET_CRATE = 1; optional Pro2 startup resources. */
|
|
87
|
-
bootResourcesBinary?: ArrayBuffer;
|
|
88
86
|
/** FW_MGMT_TARGET_APPLICATION_P1 = 4 */
|
|
89
87
|
applicationP1Binary?: ArrayBuffer;
|
|
90
88
|
/** FW_MGMT_TARGET_APPLICATION_P2 = 5 */
|
|
@@ -98,12 +96,14 @@ export interface FirmwareUpdateV4Params {
|
|
|
98
96
|
se04Binary?: ArrayBuffer;
|
|
99
97
|
forcedUpdateRes?: boolean;
|
|
100
98
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
99
|
+
* Arbitrary Protocol V2 resource files written directly with FilesystemFileWrite.
|
|
100
|
+
* Use this for manifest-driven boot resources and other non-RESC files.
|
|
103
101
|
*/
|
|
104
|
-
|
|
102
|
+
resourceFiles?: Array<{
|
|
105
103
|
binary: ArrayBuffer;
|
|
106
104
|
devicePath: string;
|
|
105
|
+
size?: number;
|
|
106
|
+
fileHash?: string;
|
|
107
107
|
}>;
|
|
108
108
|
}
|
|
109
109
|
|
package/src/types/device.ts
CHANGED
|
@@ -374,14 +374,21 @@ export type IDeviceType =
|
|
|
374
374
|
| EDeviceType.Mini
|
|
375
375
|
| EDeviceType.Touch
|
|
376
376
|
| EDeviceType.Pro
|
|
377
|
-
| EDeviceType.Pro2
|
|
377
|
+
| EDeviceType.Pro2
|
|
378
|
+
| EDeviceType.Neo;
|
|
378
379
|
|
|
379
380
|
/**
|
|
380
381
|
* model_classic: 'classic' | 'classic1s' | 'classicpure'
|
|
381
382
|
* model_mini: 'classic' | 'classic1s' | 'classicpure' | 'mini'
|
|
382
383
|
* model_touch: 'touch' | 'pro'
|
|
384
|
+
* model_pro2: 'pro2' | 'neo'
|
|
383
385
|
*/
|
|
384
|
-
export type IDeviceModel =
|
|
386
|
+
export type IDeviceModel =
|
|
387
|
+
| 'model_classic'
|
|
388
|
+
| 'model_mini'
|
|
389
|
+
| 'model_touch'
|
|
390
|
+
| 'model_classic1s'
|
|
391
|
+
| 'model_pro2';
|
|
385
392
|
|
|
386
393
|
export const DeviceModelToTypes: { [deviceModel in IDeviceModel]: IDeviceType[] } = {
|
|
387
394
|
model_mini: [
|
|
@@ -391,6 +398,7 @@ export const DeviceModelToTypes: { [deviceModel in IDeviceModel]: IDeviceType[]
|
|
|
391
398
|
EDeviceType.Mini,
|
|
392
399
|
],
|
|
393
400
|
model_touch: [EDeviceType.Touch, EDeviceType.Pro],
|
|
401
|
+
model_pro2: [EDeviceType.Pro2, EDeviceType.Neo],
|
|
394
402
|
model_classic: [EDeviceType.Classic, EDeviceType.Classic1s, EDeviceType.ClassicPure],
|
|
395
403
|
model_classic1s: [EDeviceType.Classic1s, EDeviceType.ClassicPure],
|
|
396
404
|
};
|
|
@@ -402,7 +410,8 @@ export const DeviceTypeToModels: { [deviceType in IDeviceType]: IDeviceModel[] }
|
|
|
402
410
|
[EDeviceType.Mini]: ['model_mini'],
|
|
403
411
|
[EDeviceType.Touch]: ['model_touch'],
|
|
404
412
|
[EDeviceType.Pro]: ['model_touch'],
|
|
405
|
-
[EDeviceType.Pro2]: [],
|
|
413
|
+
[EDeviceType.Pro2]: ['model_pro2'],
|
|
414
|
+
[EDeviceType.Neo]: ['model_pro2'],
|
|
406
415
|
[EDeviceType.Unknown]: [],
|
|
407
416
|
};
|
|
408
417
|
|
package/src/types/settings.ts
CHANGED
|
@@ -81,19 +81,22 @@ export type IProtocolV2Resource = {
|
|
|
81
81
|
headerHash: string;
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
/**
|
|
85
|
-
export type
|
|
86
|
-
|
|
87
|
-
target: 'CRATE';
|
|
84
|
+
/** A file from the Protocol V2 resource manifest, written directly with FileWrite. */
|
|
85
|
+
export type IProtocolV2ResourceFile = {
|
|
86
|
+
name?: string;
|
|
88
87
|
url: string;
|
|
89
|
-
|
|
88
|
+
devicePath: string;
|
|
90
89
|
size: number;
|
|
91
|
-
/** SHA-256 of the complete
|
|
90
|
+
/** SHA-256 of the complete file. */
|
|
92
91
|
fileHash: string;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** Optional Protocol V2 boot resources restored as individual files. */
|
|
95
|
+
export type IProtocolV2BootResources = {
|
|
96
|
+
required: false;
|
|
97
|
+
target: 'RES';
|
|
98
|
+
manifestUrl?: string;
|
|
99
|
+
files: IProtocolV2ResourceFile[];
|
|
97
100
|
};
|
|
98
101
|
|
|
99
102
|
export type IProtocolV2Resources = {
|
|
@@ -166,19 +169,25 @@ export type IBLEFirmwareReleaseInfo = {
|
|
|
166
169
|
};
|
|
167
170
|
|
|
168
171
|
type IKnownDevice = Exclude<IDeviceType, 'unknown'>;
|
|
172
|
+
type ILegacyKnownDevice = Exclude<IKnownDevice, 'neo'>;
|
|
173
|
+
|
|
174
|
+
type IDeviceReleaseInfo = {
|
|
175
|
+
firmware: IFirmwareReleaseInfo[];
|
|
176
|
+
/** Protocol V2 payload package set */
|
|
177
|
+
'firmware-v1'?: IFirmwareReleaseInfo[];
|
|
178
|
+
'firmware-v2'?: IFirmwareReleaseInfo[];
|
|
179
|
+
'firmware-v8'?: IFirmwareReleaseInfo[];
|
|
180
|
+
'firmware-btc-v8'?: IFirmwareReleaseInfo[];
|
|
181
|
+
ble: IBLEFirmwareReleaseInfo[];
|
|
182
|
+
/** Independent Protocol V2 resource release configuration. */
|
|
183
|
+
resources?: IProtocolV2Resources;
|
|
184
|
+
};
|
|
169
185
|
|
|
170
186
|
export type DeviceTypeMap = {
|
|
171
|
-
[k in
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
'firmware-v2'?: IFirmwareReleaseInfo[];
|
|
176
|
-
'firmware-v8'?: IFirmwareReleaseInfo[];
|
|
177
|
-
'firmware-btc-v8'?: IFirmwareReleaseInfo[];
|
|
178
|
-
ble: IBLEFirmwareReleaseInfo[];
|
|
179
|
-
/** Independent Pro2 resource release configuration. */
|
|
180
|
-
resources?: IProtocolV2Resources;
|
|
181
|
-
};
|
|
187
|
+
[k in ILegacyKnownDevice]: IDeviceReleaseInfo;
|
|
188
|
+
} & {
|
|
189
|
+
/** Optional until every remote-config producer publishes a Neo entry. */
|
|
190
|
+
neo?: IDeviceReleaseInfo;
|
|
182
191
|
};
|
|
183
192
|
|
|
184
193
|
export type AssetsMap = {
|
|
@@ -60,6 +60,8 @@ export const resolveDeviceType = (features?: DeviceFeaturesInput): IDeviceType =
|
|
|
60
60
|
return EDeviceType.Pro;
|
|
61
61
|
case 'PRO2':
|
|
62
62
|
return EDeviceType.Pro2;
|
|
63
|
+
case 'NEO':
|
|
64
|
+
return EDeviceType.Neo;
|
|
63
65
|
case 'PURE':
|
|
64
66
|
return EDeviceType.ClassicPure;
|
|
65
67
|
default:
|
|
@@ -69,6 +71,9 @@ export const resolveDeviceType = (features?: DeviceFeaturesInput): IDeviceType =
|
|
|
69
71
|
if (compatible.model?.toString().toLowerCase() === EDeviceType.Pro2) {
|
|
70
72
|
return EDeviceType.Pro2;
|
|
71
73
|
}
|
|
74
|
+
if (compatible.model?.toString().toLowerCase() === EDeviceType.Neo) {
|
|
75
|
+
return EDeviceType.Neo;
|
|
76
|
+
}
|
|
72
77
|
|
|
73
78
|
const serialNo = resolveDeviceSerialNo(features);
|
|
74
79
|
const prefix = serialNo.slice(0, 2).toLowerCase();
|
|
@@ -74,7 +74,8 @@ export const supportInputPinOnSoftware = (features?: Features): SupportFeatureTy
|
|
|
74
74
|
if (
|
|
75
75
|
deviceType === EDeviceType.Touch ||
|
|
76
76
|
deviceType === EDeviceType.Pro ||
|
|
77
|
-
deviceType === EDeviceType.Pro2
|
|
77
|
+
deviceType === EDeviceType.Pro2 ||
|
|
78
|
+
deviceType === EDeviceType.Neo
|
|
78
79
|
) {
|
|
79
80
|
return { support: false };
|
|
80
81
|
}
|
|
@@ -90,7 +91,8 @@ export const supportNewPassphrase = (features?: Features): SupportFeatureType =>
|
|
|
90
91
|
if (
|
|
91
92
|
deviceType === EDeviceType.Touch ||
|
|
92
93
|
deviceType === EDeviceType.Pro ||
|
|
93
|
-
deviceType === EDeviceType.Pro2
|
|
94
|
+
deviceType === EDeviceType.Pro2 ||
|
|
95
|
+
deviceType === EDeviceType.Neo
|
|
94
96
|
) {
|
|
95
97
|
return { support: true };
|
|
96
98
|
}
|
|
@@ -143,7 +145,8 @@ export const getPassphraseStateWithRefreshDeviceInfo = async (
|
|
|
143
145
|
const isModeT =
|
|
144
146
|
deviceType === EDeviceType.Touch ||
|
|
145
147
|
deviceType === EDeviceType.Pro ||
|
|
146
|
-
deviceType === EDeviceType.Pro2
|
|
148
|
+
deviceType === EDeviceType.Pro2 ||
|
|
149
|
+
deviceType === EDeviceType.Neo;
|
|
147
150
|
|
|
148
151
|
// 如果可以获取到 passphraseState,但是设备 features 显示设备未开启 passphrase,需要刷新设备状态
|
|
149
152
|
// if passphraseState can be obtained, but the device features show that the device has not enabled passphrase, the device status needs to be refreshed
|
|
@@ -31,6 +31,7 @@ export const getDeviceTypeByBleName = (name?: string): IDeviceType => {
|
|
|
31
31
|
if (/^Touch/i.test(name)) return EDeviceType.Touch;
|
|
32
32
|
|
|
33
33
|
if (/\bPro\s*2\b/i.test(name) || /^Pro2/i.test(name)) return EDeviceType.Pro2;
|
|
34
|
+
if (/\bNeo\b/i.test(name) || /^Neo/i.test(name)) return EDeviceType.Neo;
|
|
34
35
|
if (/\bPro\b/i.test(name) || /^Pro/i.test(name)) return EDeviceType.Pro;
|
|
35
36
|
|
|
36
37
|
return EDeviceType.Unknown;
|
|
@@ -93,6 +94,7 @@ export const getMethodVersionRange = (
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
const modelFallbacks: IDeviceModel[] = [
|
|
97
|
+
'model_pro2',
|
|
96
98
|
'model_classic1s',
|
|
97
99
|
'model_classic',
|
|
98
100
|
'model_mini',
|
|
@@ -127,6 +127,7 @@ export const getLanguageConfig = (deviceType: IDeviceType): Record<string, strin
|
|
|
127
127
|
keys = Object.keys(LANGUAGE_LABELS) as LanguageKey[];
|
|
128
128
|
break;
|
|
129
129
|
case EDeviceType.Pro2:
|
|
130
|
+
case EDeviceType.Neo:
|
|
130
131
|
return PRO2_LANGUAGE_OPTIONS.map(option => ({ ...option }));
|
|
131
132
|
default:
|
|
132
133
|
keys = [];
|
|
@@ -176,6 +177,7 @@ export const getAutoLockOptions = (
|
|
|
176
177
|
case EDeviceType.Touch:
|
|
177
178
|
case EDeviceType.Pro:
|
|
178
179
|
case EDeviceType.Pro2:
|
|
180
|
+
case EDeviceType.Neo:
|
|
179
181
|
return withNever([30_000, 60_000, 120_000, 300_000, 600_000, 1_800_000], protocol);
|
|
180
182
|
default:
|
|
181
183
|
return [];
|
|
@@ -197,6 +199,7 @@ export const getAutoShutDownOptions = (
|
|
|
197
199
|
case EDeviceType.Pro:
|
|
198
200
|
return withNever([60_000, 120_000, 300_000, 600_000], protocol);
|
|
199
201
|
case EDeviceType.Pro2:
|
|
202
|
+
case EDeviceType.Neo:
|
|
200
203
|
return withNever([60_000, 120_000, 300_000, 600_000, 1_800_000], protocol);
|
|
201
204
|
default:
|
|
202
205
|
return [];
|