@onekeyfe/hd-core 1.2.0-alpha.10 → 1.2.0-alpha.11
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 +25 -0
- package/__tests__/RequestQueue.test.ts +34 -0
- package/__tests__/pro2Wallpaper.test.ts +50 -0
- package/__tests__/protocol-v2.test.ts +426 -60
- package/dist/api/BaseMethod.d.ts +4 -0
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/FileWrite.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +0 -4
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts +1 -6
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts +18 -0
- package/dist/api/UploadPortfolio.d.ts.map +1 -0
- package/dist/api/index.d.ts +5 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceSessionGet.d.ts +1 -4
- package/dist/api/protocol-v2/DeviceSessionGet.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceSettingsGet.d.ts +6 -0
- package/dist/api/protocol-v2/DeviceSettingsGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts +16 -0
- package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceSettingsSet.d.ts +12 -0
- package/dist/api/protocol-v2/DeviceSettingsSet.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +26 -0
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -0
- package/dist/core/RequestQueue.d.ts +2 -0
- package/dist/core/RequestQueue.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.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/events/call.d.ts +7 -0
- package/dist/events/call.d.ts.map +1 -1
- package/dist/events/core.d.ts +2 -2
- package/dist/events/core.d.ts.map +1 -1
- package/dist/events/iframe.d.ts +1 -0
- package/dist/events/iframe.d.ts.map +1 -1
- package/dist/index.d.ts +76 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +694 -253
- package/dist/inject.d.ts +3 -2
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts +2 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +9 -0
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +2 -0
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/getPassphraseState.d.ts +2 -10
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +8 -2
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +19 -4
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +0 -2
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.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/pro2Wallpaper.d.ts +12 -0
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +18 -0
- package/src/api/FileWrite.ts +9 -1
- package/src/api/FirmwareUpdateV4.ts +21 -91
- package/src/api/GetPassphraseState.ts +9 -18
- package/src/api/UploadPortfolio.ts +37 -0
- package/src/api/index.ts +5 -0
- package/src/api/protocol-v2/DeviceSessionGet.ts +3 -14
- package/src/api/protocol-v2/DeviceSettingsGet.ts +16 -0
- package/src/api/protocol-v2/DeviceSettingsPageShow.ts +59 -0
- package/src/api/protocol-v2/DeviceSettingsSet.ts +39 -0
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +160 -0
- package/src/core/RequestQueue.ts +19 -0
- package/src/core/index.ts +10 -1
- package/src/data/messages/messages-protocol-v2.json +92 -8
- package/src/device/Device.ts +17 -3
- package/src/device/DeviceCommands.ts +9 -3
- package/src/events/call.ts +6 -0
- package/src/events/core.ts +2 -0
- package/src/events/iframe.ts +1 -0
- package/src/index.ts +4 -0
- package/src/inject.ts +13 -0
- package/src/lowLevelInject.ts +3 -0
- package/src/protocols/protocol-v2/unlockRetry.ts +46 -0
- package/src/protocols/protocol-v2/walletSession.ts +14 -5
- package/src/topLevelInject.ts +2 -0
- package/src/types/api/firmwareUpdate.ts +12 -0
- package/src/types/api/getPassphraseState.ts +2 -11
- package/src/types/api/index.ts +12 -1
- package/src/types/api/protocolV2.ts +46 -3
- package/src/utils/deviceFeaturesUtils.ts +3 -8
- package/src/utils/index.ts +6 -0
- package/src/utils/pro2Wallpaper.ts +111 -0
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
isProtocolV2DeviceDisconnectedError,
|
|
31
31
|
} from './protocol-v2/helpers';
|
|
32
32
|
|
|
33
|
-
import type { FirmwareUpdateV4Params } from '../types/api/firmwareUpdate';
|
|
33
|
+
import type { FirmwareUpdateV4Params, FirmwareUpdateV4Target } from '../types/api/firmwareUpdate';
|
|
34
34
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
35
35
|
import type { PROTO } from '../constants';
|
|
36
36
|
import type { TypedResponseMessage } from '../device/DeviceCommands';
|
|
@@ -188,6 +188,17 @@ const PROTOCOL_V2_REMOTE_COMPONENT_TARGETS: Readonly<
|
|
|
188
188
|
},
|
|
189
189
|
};
|
|
190
190
|
|
|
191
|
+
const PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID = new Map<number, FirmwareUpdateV4Target>([
|
|
192
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER, 'boot'],
|
|
193
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1, 'app_v1'],
|
|
194
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2, 'app_v2'],
|
|
195
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR, 'coprocessor'],
|
|
196
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01, 'se01'],
|
|
197
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02, 'se02'],
|
|
198
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03, 'se03'],
|
|
199
|
+
[ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04, 'se04'],
|
|
200
|
+
]);
|
|
201
|
+
|
|
191
202
|
const PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE =
|
|
192
203
|
'FW_MGMT_TARGET_ROMLOADER is not accepted by the current Pro2 bootloader update request. Flash romloader with the loader-specific flow instead of firmwareUpdateV4.';
|
|
193
204
|
|
|
@@ -282,13 +293,6 @@ const versionArrayToNumber = (version?: IVersionArray) => {
|
|
|
282
293
|
return version[0] * 0x10000 + version[1] * 0x100 + version[2];
|
|
283
294
|
};
|
|
284
295
|
|
|
285
|
-
const versionStringToArray = (version?: string | null): IVersionArray | undefined => {
|
|
286
|
-
if (!version) return undefined;
|
|
287
|
-
const parts = version.split('.').map(part => Number(part));
|
|
288
|
-
if (parts.length !== 3 || parts.some(part => !Number.isFinite(part))) return undefined;
|
|
289
|
-
return parts as IVersionArray;
|
|
290
|
-
};
|
|
291
|
-
|
|
292
296
|
const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
|
|
293
297
|
const currentNumber = versionArrayToNumber(current);
|
|
294
298
|
const targetNumber = versionArrayToNumber(target);
|
|
@@ -418,6 +422,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
418
422
|
{ name: 'se03Binary', type: 'buffer' },
|
|
419
423
|
{ name: 'se04Binary', type: 'buffer' },
|
|
420
424
|
{ name: 'firmwareType', type: 'string' },
|
|
425
|
+
{ name: 'targetsToUpdate', type: 'array', allowEmpty: true },
|
|
421
426
|
{ name: 'platform', type: 'string' },
|
|
422
427
|
{ name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
|
|
423
428
|
]);
|
|
@@ -436,6 +441,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
436
441
|
se04Binary: payload.se04Binary,
|
|
437
442
|
resourceBundleFiles: payload.resourceBundleFiles,
|
|
438
443
|
firmwareType: payload.firmwareType,
|
|
444
|
+
targetsToUpdate: payload.targetsToUpdate,
|
|
439
445
|
platform: payload.platform,
|
|
440
446
|
};
|
|
441
447
|
}
|
|
@@ -601,63 +607,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
601
607
|
return target;
|
|
602
608
|
}
|
|
603
609
|
|
|
604
|
-
private getProtocolV2ComponentTargetVersion(
|
|
605
|
-
release: IFirmwareReleaseInfo,
|
|
606
|
-
component: IProtocolV2FirmwareComponent,
|
|
607
|
-
target: ProtocolV2RemoteComponentTarget
|
|
608
|
-
) {
|
|
609
|
-
if (component.version) return component.version;
|
|
610
|
-
if (target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER) {
|
|
611
|
-
return release.bootloaderVersion;
|
|
612
|
-
}
|
|
613
|
-
if (
|
|
614
|
-
target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1 ||
|
|
615
|
-
target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2
|
|
616
|
-
) {
|
|
617
|
-
return release.version;
|
|
618
|
-
}
|
|
619
|
-
return undefined;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
private getProtocolV2ComponentCurrentVersion(
|
|
623
|
-
features: Features,
|
|
624
|
-
target: ProtocolV2RemoteComponentTarget
|
|
625
|
-
) {
|
|
626
|
-
switch (target.targetId) {
|
|
627
|
-
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER:
|
|
628
|
-
return getDeviceBootloaderVersion(features);
|
|
629
|
-
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1:
|
|
630
|
-
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2:
|
|
631
|
-
return getDeviceFirmwareVersion(features);
|
|
632
|
-
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR:
|
|
633
|
-
return getDeviceBLEFirmwareVersion(features);
|
|
634
|
-
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01:
|
|
635
|
-
return versionStringToArray(features.se01Version);
|
|
636
|
-
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02:
|
|
637
|
-
return versionStringToArray(features.se02Version);
|
|
638
|
-
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03:
|
|
639
|
-
return versionStringToArray(features.se03Version);
|
|
640
|
-
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04:
|
|
641
|
-
return versionStringToArray(features.se04Version);
|
|
642
|
-
default:
|
|
643
|
-
return undefined;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
private isProtocolV2ComponentVersionSatisfied(
|
|
648
|
-
release: IFirmwareReleaseInfo,
|
|
649
|
-
component: IProtocolV2FirmwareComponent,
|
|
650
|
-
target: ProtocolV2RemoteComponentTarget,
|
|
651
|
-
features: Features
|
|
652
|
-
) {
|
|
653
|
-
const targetVersion = this.getProtocolV2ComponentTargetVersion(release, component, target);
|
|
654
|
-
if (!targetVersion) return false;
|
|
655
|
-
|
|
656
|
-
const currentVersion = this.getProtocolV2ComponentCurrentVersion(features, target);
|
|
657
|
-
const compareResult = compareProtocolV2Versions(currentVersion, targetVersion);
|
|
658
|
-
return compareResult !== undefined && compareResult >= 0;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
610
|
private getProtocolV2ResourceFilePath(path: string) {
|
|
662
611
|
if (path.startsWith('vol')) return path;
|
|
663
612
|
if (path.startsWith('/')) return `vol0:${path}`;
|
|
@@ -709,25 +658,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
709
658
|
return parseProtocolV2OkppHeader(headerBytes);
|
|
710
659
|
}
|
|
711
660
|
|
|
712
|
-
private shouldInstallRemoteProtocolV2Component(
|
|
713
|
-
release: IFirmwareReleaseInfo,
|
|
714
|
-
key: string,
|
|
715
|
-
component: IProtocolV2FirmwareComponent,
|
|
716
|
-
target: ProtocolV2RemoteComponentTarget,
|
|
717
|
-
features: Features
|
|
718
|
-
) {
|
|
719
|
-
const versionSatisfied = this.isProtocolV2ComponentVersionSatisfied(
|
|
720
|
-
release,
|
|
721
|
-
component,
|
|
722
|
-
target,
|
|
723
|
-
features
|
|
724
|
-
);
|
|
725
|
-
if (versionSatisfied) {
|
|
726
|
-
Log.log(`[FirmwareUpdateV4] skip Protocol V2 component ${key}; version is up to date`);
|
|
727
|
-
}
|
|
728
|
-
return !versionSatisfied;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
661
|
private async downloadRemoteProtocolV2Component(
|
|
732
662
|
key: string,
|
|
733
663
|
component: IProtocolV2FirmwareComponent
|
|
@@ -763,16 +693,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
763
693
|
}
|
|
764
694
|
|
|
765
695
|
const entries = this.getRemoteComponentEntries(release);
|
|
696
|
+
const targetsToUpdate = new Set(this.params.targetsToUpdate ?? []);
|
|
766
697
|
|
|
767
698
|
for (const [key, component] of entries) {
|
|
768
699
|
const target = this.getRemoteComponentTarget(key, component);
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
key,
|
|
772
|
-
component,
|
|
773
|
-
target,
|
|
774
|
-
features
|
|
775
|
-
);
|
|
700
|
+
const updateTarget = PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId);
|
|
701
|
+
const shouldInstall = updateTarget ? targetsToUpdate.has(updateTarget) : false;
|
|
776
702
|
if (shouldInstall) {
|
|
777
703
|
const remoteBinary = await this.downloadRemoteProtocolV2Component(key, component);
|
|
778
704
|
if (remoteBinary.kind === 'bootloader') {
|
|
@@ -827,6 +753,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
827
753
|
}));
|
|
828
754
|
}
|
|
829
755
|
|
|
756
|
+
if (!this.params.targetsToUpdate?.includes('resource')) {
|
|
757
|
+
return undefined;
|
|
758
|
+
}
|
|
759
|
+
|
|
830
760
|
// 模式2:远端配置模式,后续按需下载 + 比对
|
|
831
761
|
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
832
762
|
if (!release?.resourceBundles?.length) return undefined;
|
|
@@ -11,27 +11,18 @@ export default class GetPassphraseState extends BaseMethod {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
async run() {
|
|
14
|
-
const { passphraseState
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
initSession: this.payload.initSession,
|
|
20
|
-
});
|
|
14
|
+
const { passphraseState } = await getPassphraseStateWithRefreshDeviceInfo(this.device, {
|
|
15
|
+
expectPassphraseState: this.payload.passphraseState,
|
|
16
|
+
onlyMainPin: this.payload.useEmptyPassphrase,
|
|
17
|
+
initSession: this.payload.initSession,
|
|
18
|
+
});
|
|
21
19
|
|
|
22
|
-
const
|
|
23
|
-
const passphraseProtection = this.device.getCurrentPassphraseProtection() ?? null;
|
|
20
|
+
const passphraseProtection = this.device.getCurrentPassphraseProtection();
|
|
24
21
|
const deviceType = this.device.getCurrentDeviceType();
|
|
25
22
|
const isProSeries = deviceType === EDeviceType.Pro || deviceType === EDeviceType.Pro2;
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
sessionId: this.payload.initSession
|
|
31
|
-
? newSession ?? undefined
|
|
32
|
-
: newSession ?? features?.sessionId ?? undefined,
|
|
33
|
-
unlockedAttachPin: unlockedAttachPin ?? features?.unlockedAttachPin,
|
|
34
|
-
passphraseProtection,
|
|
35
|
-
});
|
|
24
|
+
return Promise.resolve(
|
|
25
|
+
isProSeries || passphraseProtection === true ? passphraseState : undefined
|
|
26
|
+
);
|
|
36
27
|
}
|
|
37
28
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import FileWrite from './FileWrite';
|
|
2
|
+
|
|
3
|
+
export type UploadPortfolioParams = {
|
|
4
|
+
packageBytes: ArrayBuffer | Uint8Array | Blob;
|
|
5
|
+
operationId?: string;
|
|
6
|
+
timeoutMs?: number | string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const PORTFOLIO_PENDING_PATH = 'vol1:/portfolio/portfolio.pfol.pending';
|
|
10
|
+
const PORTFOLIO_CHUNK_SIZE = 2048;
|
|
11
|
+
|
|
12
|
+
export default class UploadPortfolio extends FileWrite {
|
|
13
|
+
init() {
|
|
14
|
+
const { packageBytes, timeoutMs } = this.payload as UploadPortfolioParams;
|
|
15
|
+
this.payload = {
|
|
16
|
+
...this.payload,
|
|
17
|
+
path: PORTFOLIO_PENDING_PATH,
|
|
18
|
+
offset: 0,
|
|
19
|
+
data: packageBytes,
|
|
20
|
+
chunkSize: PORTFOLIO_CHUNK_SIZE,
|
|
21
|
+
overwrite: true,
|
|
22
|
+
append: false,
|
|
23
|
+
timeoutMs,
|
|
24
|
+
};
|
|
25
|
+
super.init();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async run() {
|
|
29
|
+
const stagedFile = await super.run();
|
|
30
|
+
this.throwIfAborted();
|
|
31
|
+
await this.device.commands.typedCall('PortfolioUpdate', 'Success', {});
|
|
32
|
+
return {
|
|
33
|
+
...stagedFile,
|
|
34
|
+
portfolioUpdated: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/api/index.ts
CHANGED
|
@@ -54,6 +54,10 @@ export { default as deviceFirmwareUpdate } from './protocol-v2/DeviceFirmwareUpd
|
|
|
54
54
|
export { default as deviceGetFirmwareUpdateStatus } from './protocol-v2/DeviceGetFirmwareUpdateStatus';
|
|
55
55
|
export { default as deviceFactoryInfoSet } from './protocol-v2/DeviceFactoryInfoSet';
|
|
56
56
|
export { default as deviceFactoryInfoGet } from './protocol-v2/DeviceFactoryInfoGet';
|
|
57
|
+
export { default as deviceSettingsGet } from './protocol-v2/DeviceSettingsGet';
|
|
58
|
+
export { default as deviceSettingsSet } from './protocol-v2/DeviceSettingsSet';
|
|
59
|
+
export { default as deviceSettingsPageShow } from './protocol-v2/DeviceSettingsPageShow';
|
|
60
|
+
export { default as deviceUploadWallpaper } from './protocol-v2/DeviceUploadWallpaper';
|
|
57
61
|
export { default as filesystemPermissionFix } from './protocol-v2/FilesystemPermissionFix';
|
|
58
62
|
export { default as filesystemFormat } from './protocol-v2/FilesystemFormat';
|
|
59
63
|
export { default as filesystemDiskControl } from './protocol-v2/FilesystemDiskControl';
|
|
@@ -66,6 +70,7 @@ export { default as dirRemove } from './DirRemove';
|
|
|
66
70
|
export { default as pathInfo } from './PathInfo';
|
|
67
71
|
export { default as filesystemFileRead } from './FileRead';
|
|
68
72
|
export { default as filesystemFileWrite } from './FileWrite';
|
|
73
|
+
export { default as uploadPortfolio } from './UploadPortfolio';
|
|
69
74
|
export { default as filesystemFileDelete } from './FileDelete';
|
|
70
75
|
export { default as filesystemDirList } from './DirList';
|
|
71
76
|
export { default as filesystemDirMake } from './DirMake';
|
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
import { BaseMethod } from '../BaseMethod';
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
sessionId?: string;
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export default class DeviceSessionGet extends BaseMethod<DeviceSessionGetParams> {
|
|
3
|
+
export default class DeviceSessionGet extends BaseMethod {
|
|
8
4
|
init() {
|
|
9
5
|
this.requireProtocolV2 = true;
|
|
10
6
|
this.useDevicePassphraseState = false;
|
|
11
7
|
this.skipForceUpdateCheck = true;
|
|
12
|
-
this.params =
|
|
13
|
-
sessionId: this.payload.sessionId,
|
|
14
|
-
};
|
|
8
|
+
this.params = undefined;
|
|
15
9
|
}
|
|
16
10
|
|
|
17
11
|
async run() {
|
|
18
|
-
const
|
|
19
|
-
const { message } = await this.device.commands.typedCall(
|
|
20
|
-
'DeviceSessionGet',
|
|
21
|
-
'DeviceSession',
|
|
22
|
-
payload
|
|
23
|
-
);
|
|
12
|
+
const { message } = await this.device.commands.typedCall('DeviceSessionGet', 'DeviceSession', {});
|
|
24
13
|
return message;
|
|
25
14
|
}
|
|
26
15
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseMethod } from '../BaseMethod';
|
|
2
|
+
|
|
3
|
+
export default class DeviceSettingsGet extends BaseMethod {
|
|
4
|
+
init() {
|
|
5
|
+
this.requireProtocolV2 = true;
|
|
6
|
+
this.unlockPolicy = 'retry-on-locked';
|
|
7
|
+
this.skipForceUpdateCheck = true;
|
|
8
|
+
this.useDevicePassphraseState = false;
|
|
9
|
+
this.params = undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async run() {
|
|
13
|
+
const res = await this.device.commands.typedCall('DeviceSettingsGet', 'DeviceSettings', {});
|
|
14
|
+
return Promise.resolve(res.message);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
2
|
+
|
|
3
|
+
import { BaseMethod } from '../BaseMethod';
|
|
4
|
+
import { invalidParameter } from '../helpers/filesystemValidation';
|
|
5
|
+
|
|
6
|
+
export type SupportedDeviceSettingsPage = DeviceSettingsPage;
|
|
7
|
+
|
|
8
|
+
export type DeviceSettingsPageShowParams = {
|
|
9
|
+
page?:
|
|
10
|
+
| SupportedDeviceSettingsPage
|
|
11
|
+
| 'DeviceReset'
|
|
12
|
+
| 'DevicePinChange'
|
|
13
|
+
| 'DevicePassphrase'
|
|
14
|
+
| 'DeviceAirgap';
|
|
15
|
+
fieldName?: string;
|
|
16
|
+
field_name?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const SUPPORTED_PAGES = new Set<number>([
|
|
20
|
+
DeviceSettingsPage.DeviceReset,
|
|
21
|
+
DeviceSettingsPage.DevicePinChange,
|
|
22
|
+
DeviceSettingsPage.DevicePassphrase,
|
|
23
|
+
DeviceSettingsPage.DeviceAirgap,
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
function normalizePage(value: DeviceSettingsPageShowParams['page']): SupportedDeviceSettingsPage {
|
|
27
|
+
const page = typeof value === 'string' ? DeviceSettingsPage[value] : value;
|
|
28
|
+
if (page !== undefined && SUPPORTED_PAGES.has(page)) {
|
|
29
|
+
return page as SupportedDeviceSettingsPage;
|
|
30
|
+
}
|
|
31
|
+
throw invalidParameter(
|
|
32
|
+
'Parameter [page] must be DeviceReset, DevicePinChange, DevicePassphrase, or DeviceAirgap.'
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default class DeviceSettingsPageShow extends BaseMethod<{
|
|
37
|
+
page: SupportedDeviceSettingsPage;
|
|
38
|
+
field_name?: string;
|
|
39
|
+
}> {
|
|
40
|
+
init() {
|
|
41
|
+
this.requireProtocolV2 = true;
|
|
42
|
+
this.unlockPolicy = 'retry-on-locked';
|
|
43
|
+
this.skipForceUpdateCheck = true;
|
|
44
|
+
this.useDevicePassphraseState = false;
|
|
45
|
+
this.params = {
|
|
46
|
+
page: normalizePage(this.payload.page),
|
|
47
|
+
field_name: this.payload.field_name ?? this.payload.fieldName,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async run() {
|
|
52
|
+
const res = await this.device.commands.typedCall(
|
|
53
|
+
'DeviceSettingsPageShow',
|
|
54
|
+
'Success',
|
|
55
|
+
this.params
|
|
56
|
+
);
|
|
57
|
+
return Promise.resolve(res.message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { BaseMethod } from '../BaseMethod';
|
|
2
|
+
import { invalidParameter } from '../helpers/filesystemValidation';
|
|
3
|
+
|
|
4
|
+
import type { DeviceSettings } from '@onekeyfe/hd-transport';
|
|
5
|
+
|
|
6
|
+
export type DeviceSettingsSetParams = {
|
|
7
|
+
settings?: Omit<DeviceSettings, 'passphrase_enable' | 'airgap_mode'>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default class DeviceSettingsSet extends BaseMethod<{
|
|
11
|
+
settings: Omit<DeviceSettings, 'passphrase_enable' | 'airgap_mode'>;
|
|
12
|
+
}> {
|
|
13
|
+
init() {
|
|
14
|
+
const settings = this.payload.settings;
|
|
15
|
+
if (!settings || typeof settings !== 'object' || Array.isArray(settings)) {
|
|
16
|
+
throw invalidParameter('Parameter [settings] must be an object.');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
passphrase_enable: _passphraseEnable,
|
|
21
|
+
airgap_mode: _airgapMode,
|
|
22
|
+
...supported
|
|
23
|
+
} = settings as DeviceSettings;
|
|
24
|
+
if (Object.keys(supported).length === 0) {
|
|
25
|
+
throw invalidParameter('Parameter [settings] must contain at least one supported setting.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.requireProtocolV2 = true;
|
|
29
|
+
this.unlockPolicy = 'retry-on-locked';
|
|
30
|
+
this.skipForceUpdateCheck = true;
|
|
31
|
+
this.useDevicePassphraseState = false;
|
|
32
|
+
this.params = { settings: supported };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async run() {
|
|
36
|
+
const res = await this.device.commands.typedCall('DeviceSettingsSet', 'Success', this.params);
|
|
37
|
+
return Promise.resolve(res.message);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { blake2s } from '@noble/hashes/blake2s';
|
|
2
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
3
|
+
import {
|
|
4
|
+
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
5
|
+
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
6
|
+
WallpaperTarget,
|
|
7
|
+
} from '@onekeyfe/hd-transport';
|
|
8
|
+
|
|
9
|
+
import { BaseMethod } from '../BaseMethod';
|
|
10
|
+
import { invalidParameter } from '../helpers/filesystemValidation';
|
|
11
|
+
import { DataManager } from '../../data-manager';
|
|
12
|
+
import {
|
|
13
|
+
encodePro2Wallpaper,
|
|
14
|
+
PRO2_WALLPAPER_HEIGHT,
|
|
15
|
+
PRO2_WALLPAPER_WIDTH,
|
|
16
|
+
type Pro2WallpaperColorFormat,
|
|
17
|
+
} from '../../utils/pro2Wallpaper';
|
|
18
|
+
|
|
19
|
+
export type DeviceUploadWallpaperParams = {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
rgba: Uint8Array | ArrayBuffer;
|
|
23
|
+
fileName?: string;
|
|
24
|
+
chunkSize?: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type DeviceUploadWallpaperResponse = {
|
|
28
|
+
path: string;
|
|
29
|
+
size: number;
|
|
30
|
+
colorFormat: Pro2WallpaperColorFormat;
|
|
31
|
+
message?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const WALLPAPER_DIRECTORY = 'vol0:/wallpapers/user';
|
|
35
|
+
const SAFE_FILE_NAME = /^[A-Za-z0-9_-]+(?:\.bin)?$/;
|
|
36
|
+
|
|
37
|
+
function getDefaultChunkSize(): number {
|
|
38
|
+
const env = DataManager.getSettings('env');
|
|
39
|
+
return env && DataManager.isBleConnect(env)
|
|
40
|
+
? PROTOCOL_V2_BLE_FILE_CHUNK_SIZE
|
|
41
|
+
: PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeFileName(fileName: string | undefined, data: Uint8Array): string {
|
|
45
|
+
if (fileName !== undefined && (!fileName || !SAFE_FILE_NAME.test(fileName))) {
|
|
46
|
+
throw invalidParameter(
|
|
47
|
+
'Parameter [fileName] may only contain letters, numbers, underscores, hyphens and an optional .bin suffix.'
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const baseName = fileName ?? `wallpaper-${bytesToHex(blake2s(data)).slice(0, 12)}`;
|
|
51
|
+
return baseName.endsWith('.bin') ? baseName : `${baseName}.bin`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpaperParams> {
|
|
55
|
+
private encoded?: { data: Uint8Array; colorFormat: Pro2WallpaperColorFormat };
|
|
56
|
+
|
|
57
|
+
private directoryReady = false;
|
|
58
|
+
|
|
59
|
+
private uploaded = false;
|
|
60
|
+
|
|
61
|
+
private path = '';
|
|
62
|
+
|
|
63
|
+
init() {
|
|
64
|
+
const { width, height, rgba, fileName, chunkSize } = this.payload;
|
|
65
|
+
if (width !== PRO2_WALLPAPER_WIDTH || height !== PRO2_WALLPAPER_HEIGHT) {
|
|
66
|
+
throw invalidParameter(
|
|
67
|
+
`Pro2 wallpaper dimensions must be ${PRO2_WALLPAPER_WIDTH}x${PRO2_WALLPAPER_HEIGHT}.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (!(rgba instanceof ArrayBuffer) && !ArrayBuffer.isView(rgba)) {
|
|
71
|
+
throw invalidParameter('Parameter [rgba] must be an ArrayBuffer or Uint8Array.');
|
|
72
|
+
}
|
|
73
|
+
if (chunkSize !== undefined && (!Number.isInteger(chunkSize) || chunkSize <= 0)) {
|
|
74
|
+
throw invalidParameter('Parameter [chunkSize] must be a positive integer.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const rgbaBytes =
|
|
78
|
+
rgba instanceof ArrayBuffer
|
|
79
|
+
? rgba
|
|
80
|
+
: new Uint8Array(rgba.buffer, rgba.byteOffset, rgba.byteLength);
|
|
81
|
+
this.encoded = encodePro2Wallpaper({ width, height, rgba: rgbaBytes });
|
|
82
|
+
this.path = `${WALLPAPER_DIRECTORY}/${normalizeFileName(fileName, this.encoded.data)}`;
|
|
83
|
+
this.params = { width, height, rgba: rgbaBytes, fileName, chunkSize };
|
|
84
|
+
this.requireProtocolV2 = true;
|
|
85
|
+
this.unlockPolicy = 'retry-on-locked';
|
|
86
|
+
this.skipForceUpdateCheck = true;
|
|
87
|
+
this.useDevicePassphraseState = false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private async ensureDirectory() {
|
|
91
|
+
if (this.directoryReady) return;
|
|
92
|
+
try {
|
|
93
|
+
await this.device.commands.typedCall('FilesystemDirMake', 'Success', {
|
|
94
|
+
path: WALLPAPER_DIRECTORY,
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
98
|
+
if (!/exist/i.test(message)) throw error;
|
|
99
|
+
}
|
|
100
|
+
this.directoryReady = true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private async upload() {
|
|
104
|
+
if (this.uploaded) return;
|
|
105
|
+
const encoded = this.encoded;
|
|
106
|
+
if (!encoded) throw invalidParameter('Wallpaper data has not been initialized.');
|
|
107
|
+
|
|
108
|
+
const maxChunkSize = getDefaultChunkSize();
|
|
109
|
+
const requestedChunkSize = this.params.chunkSize ?? maxChunkSize;
|
|
110
|
+
const chunkSize = Math.min(Math.max(64, Math.floor(requestedChunkSize)), maxChunkSize);
|
|
111
|
+
let offset = 0;
|
|
112
|
+
while (offset < encoded.data.byteLength) {
|
|
113
|
+
const chunk = encoded.data.slice(offset, Math.min(offset + chunkSize, encoded.data.byteLength));
|
|
114
|
+
const response = await this.device.commands.typedCall(
|
|
115
|
+
'FilesystemFileWrite',
|
|
116
|
+
'FilesystemFile',
|
|
117
|
+
{
|
|
118
|
+
file: {
|
|
119
|
+
path: this.path,
|
|
120
|
+
offset,
|
|
121
|
+
total_size: encoded.data.byteLength,
|
|
122
|
+
data: chunk,
|
|
123
|
+
},
|
|
124
|
+
overwrite: offset === 0,
|
|
125
|
+
append: false,
|
|
126
|
+
ui_percentage: Math.min(
|
|
127
|
+
Math.ceil(((offset + chunk.byteLength) / encoded.data.byteLength) * 100),
|
|
128
|
+
100
|
|
129
|
+
),
|
|
130
|
+
},
|
|
131
|
+
{ timeoutMs: undefined }
|
|
132
|
+
);
|
|
133
|
+
const processedByte = Number(response.message?.processed_byte);
|
|
134
|
+
offset = Number.isFinite(processedByte) && processedByte > offset
|
|
135
|
+
? processedByte
|
|
136
|
+
: offset + chunk.byteLength;
|
|
137
|
+
if (offset > encoded.data.byteLength) {
|
|
138
|
+
throw invalidParameter(`Invalid processed_byte returned by device: ${processedByte}.`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
this.uploaded = true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async run(): Promise<DeviceUploadWallpaperResponse> {
|
|
145
|
+
const encoded = this.encoded;
|
|
146
|
+
if (!encoded) throw invalidParameter('Wallpaper data has not been initialized.');
|
|
147
|
+
await this.ensureDirectory();
|
|
148
|
+
await this.upload();
|
|
149
|
+
const response = await this.device.commands.typedCall('SetWallpaper', 'Success', {
|
|
150
|
+
target: WallpaperTarget.Lock,
|
|
151
|
+
path: this.path,
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
path: this.path,
|
|
155
|
+
size: encoded.data.byteLength,
|
|
156
|
+
colorFormat: encoded.colorFormat,
|
|
157
|
+
message: response.message?.message,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
package/src/core/RequestQueue.ts
CHANGED
|
@@ -14,6 +14,8 @@ export type RequestTask = {
|
|
|
14
14
|
export default class RequestQueue {
|
|
15
15
|
private requestQueue = new Map<number, RequestTask>();
|
|
16
16
|
|
|
17
|
+
private operationRequestIds = new Map<string, number>();
|
|
18
|
+
|
|
17
19
|
private pendingCallbackTasks = new Map<string, Deferred<void>>();
|
|
18
20
|
|
|
19
21
|
// 生成唯一请求ID
|
|
@@ -30,11 +32,21 @@ export default class RequestQueue {
|
|
|
30
32
|
method.responseID = requestId;
|
|
31
33
|
}
|
|
32
34
|
const abortController = new AbortController();
|
|
35
|
+
method.abortSignal = abortController.signal;
|
|
33
36
|
const task = { id: requestId, method, abortController };
|
|
34
37
|
this.requestQueue.set(requestId, task);
|
|
38
|
+
const operationId = method.payload?.operationId;
|
|
39
|
+
if (typeof operationId === 'string' && operationId) {
|
|
40
|
+
this.operationRequestIds.set(operationId, requestId);
|
|
41
|
+
}
|
|
35
42
|
return task;
|
|
36
43
|
}
|
|
37
44
|
|
|
45
|
+
public abortOperation(operationId: string) {
|
|
46
|
+
const requestId = this.operationRequestIds.get(operationId);
|
|
47
|
+
return requestId === undefined ? false : this.abortRequest(requestId);
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
public getTask(requestId: number): RequestTask | undefined {
|
|
39
51
|
return this.requestQueue.get(requestId);
|
|
40
52
|
}
|
|
@@ -105,6 +117,13 @@ export default class RequestQueue {
|
|
|
105
117
|
|
|
106
118
|
// 删除请求
|
|
107
119
|
public releaseTask(requestId: number) {
|
|
120
|
+
const operationId = this.requestQueue.get(requestId)?.method.payload?.operationId;
|
|
121
|
+
if (
|
|
122
|
+
typeof operationId === 'string' &&
|
|
123
|
+
this.operationRequestIds.get(operationId) === requestId
|
|
124
|
+
) {
|
|
125
|
+
this.operationRequestIds.delete(operationId);
|
|
126
|
+
}
|
|
108
127
|
this.requestQueue.delete(requestId);
|
|
109
128
|
}
|
|
110
129
|
|
package/src/core/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ import TransportManager from '../data-manager/TransportManager';
|
|
|
56
56
|
import DeviceConnector from '../device/DeviceConnector';
|
|
57
57
|
import RequestQueue from './RequestQueue';
|
|
58
58
|
import { getSynchronize } from '../utils/getSynchronize';
|
|
59
|
+
import { runMethodWithUnlockRetry } from '../protocols/protocol-v2/unlockRetry';
|
|
59
60
|
|
|
60
61
|
import type { ConnectSettings, Features, KnownDevice } from '../types';
|
|
61
62
|
import type { CoreMessage, IFrameCallMessage, UiPromise, UiPromiseResponse } from '../events';
|
|
@@ -616,7 +617,7 @@ const onCallDevice = async (
|
|
|
616
617
|
method.device?.commands?.checkDisposed();
|
|
617
618
|
|
|
618
619
|
try {
|
|
619
|
-
const response: object = await method
|
|
620
|
+
const response: object = await runMethodWithUnlockRetry(method, device);
|
|
620
621
|
messageResponse = createResponseMessage(method.responseID, true, response);
|
|
621
622
|
requestQueue.resolveRequest(method.responseID, messageResponse);
|
|
622
623
|
completeMethodRequestContext(method);
|
|
@@ -1341,6 +1342,10 @@ export default class Core extends EventEmitter {
|
|
|
1341
1342
|
Log.debug(`[Core] Created SDK instance: ${this.sdkInstanceId}`);
|
|
1342
1343
|
}
|
|
1343
1344
|
|
|
1345
|
+
cancelOperation(operationId: string) {
|
|
1346
|
+
this.requestQueue.abortOperation(operationId);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1344
1349
|
private getCoreContext() {
|
|
1345
1350
|
return {
|
|
1346
1351
|
sdkInstanceId: this.sdkInstanceId,
|
|
@@ -1409,6 +1414,10 @@ export default class Core extends EventEmitter {
|
|
|
1409
1414
|
cancel(this.getCoreContext(), message.payload.connectId);
|
|
1410
1415
|
break;
|
|
1411
1416
|
}
|
|
1417
|
+
case IFRAME.CANCEL_OPERATION: {
|
|
1418
|
+
this.cancelOperation(message.payload.operationId);
|
|
1419
|
+
break;
|
|
1420
|
+
}
|
|
1412
1421
|
case IFRAME.CALLBACK: {
|
|
1413
1422
|
Log.log('callback message: ', message);
|
|
1414
1423
|
postMessage(message);
|