@onekeyfe/hd-core 1.2.0-alpha.76 → 1.2.0-alpha.78
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__/device-state-mapper.test.ts +16 -1
- package/__tests__/device-state-projector.test.ts +20 -0
- package/__tests__/device-state-store.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +57 -0
- package/__tests__/homescreen.test.ts +17 -0
- package/__tests__/method-protocol-support.test.ts +13 -1
- package/__tests__/protocol-v2-resources.test.ts +11 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/utils.d.ts +2 -0
- package/dist/api/utils.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/device/DeviceStateProjector.d.ts.map +1 -1
- package/dist/device/DeviceStateStore.d.ts.map +1 -1
- package/dist/index.d.ts +23 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +269 -99
- package/dist/types/device.d.ts +8 -0
- package/dist/types/device.d.ts.map +1 -1
- package/dist/utils/homescreen.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV2.ts +12 -0
- package/src/api/FirmwareUpdateV3.ts +2 -1
- package/src/api/utils.ts +25 -0
- package/src/data-manager/DataManager.ts +15 -0
- package/src/device/DeviceStateMapper.ts +42 -0
- package/src/device/DeviceStateProjector.ts +58 -0
- package/src/device/DeviceStateStore.ts +31 -0
- package/src/index.ts +2 -0
- package/src/types/device.ts +16 -0
- package/src/utils/homescreen.ts +5 -1
package/src/api/utils.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as ApiMethods from './index';
|
|
|
4
4
|
|
|
5
5
|
import type { BaseMethod } from './BaseMethod';
|
|
6
6
|
import type { IFrameCallMessage } from '../events';
|
|
7
|
+
import type { ProtocolType } from '@onekeyfe/hd-transport';
|
|
7
8
|
|
|
8
9
|
type MethodConstructor = new (message: IFrameCallMessage & { id?: number }) => BaseMethod<any>;
|
|
9
10
|
|
|
@@ -25,3 +26,27 @@ export function findMethod(message: IFrameCallMessage & { id?: number }): BaseMe
|
|
|
25
26
|
`Method ${method} is not set`
|
|
26
27
|
);
|
|
27
28
|
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Read the protocol contract from the same method instance used by the Core dispatcher.
|
|
32
|
+
* Passing a payload initializes the method first so parameter-dependent contracts, such
|
|
33
|
+
* as Bitcoin fork support, are evaluated with the actual request.
|
|
34
|
+
*/
|
|
35
|
+
export function getMethodSupportedProtocols(
|
|
36
|
+
method: string,
|
|
37
|
+
payload?: Record<string, unknown>
|
|
38
|
+
): readonly ProtocolType[] {
|
|
39
|
+
const instance = findMethod({
|
|
40
|
+
id: 0,
|
|
41
|
+
payload: {
|
|
42
|
+
...payload,
|
|
43
|
+
method,
|
|
44
|
+
},
|
|
45
|
+
} as unknown as IFrameCallMessage);
|
|
46
|
+
|
|
47
|
+
if (payload !== undefined) {
|
|
48
|
+
instance.init();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return instance.getSupportedProtocols();
|
|
52
|
+
}
|
|
@@ -32,6 +32,8 @@ import type {
|
|
|
32
32
|
RemoteConfigResponse,
|
|
33
33
|
} from '../types';
|
|
34
34
|
|
|
35
|
+
const FIRMWARE_UPDATE_CONFIG_FRESHNESS_MS = 5 * 60 * 1000;
|
|
36
|
+
|
|
35
37
|
const Log = getLogger(LoggerNames.Core);
|
|
36
38
|
|
|
37
39
|
export const FIRMWARE_FIELDS = [
|
|
@@ -507,6 +509,7 @@ export default class DataManager {
|
|
|
507
509
|
this.assets = {
|
|
508
510
|
bridge: data.bridge,
|
|
509
511
|
};
|
|
512
|
+
this.lastCheckTimestamp = getTimeStamp();
|
|
510
513
|
}
|
|
511
514
|
|
|
512
515
|
static updateEnv(newEnv: ConnectSettings['env']) {
|
|
@@ -538,6 +541,18 @@ export default class DataManager {
|
|
|
538
541
|
if (!this.settings) {
|
|
539
542
|
throw new Error('Remote config settings are not initialized');
|
|
540
543
|
}
|
|
544
|
+
const hasFreshConfig =
|
|
545
|
+
this.lastCheckTimestamp > 0 &&
|
|
546
|
+
getTimeStamp() - this.lastCheckTimestamp <= FIRMWARE_UPDATE_CONFIG_FRESHNESS_MS;
|
|
547
|
+
if (hasFreshConfig) {
|
|
548
|
+
if (requireResources && this.protocolV2ResourcesConfigError) {
|
|
549
|
+
throw ERRORS.TypedError(
|
|
550
|
+
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
551
|
+
`Invalid Pro2 resources config: ${this.protocolV2ResourcesConfigError.message}`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
541
556
|
const loaded = await this.load(this.settings);
|
|
542
557
|
if (!loaded) {
|
|
543
558
|
throw ERRORS.TypedError(
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
mapLanguageToProtocolV2,
|
|
8
8
|
normalizeSafetyCheckLevel,
|
|
9
9
|
} from '../utils/deviceSettings';
|
|
10
|
+
import { getProtocolV2SeState, getProtocolV2SeType } from '../protocols/protocol-v2/features';
|
|
10
11
|
|
|
11
12
|
import type {
|
|
12
13
|
ApplySettings,
|
|
@@ -159,6 +160,11 @@ const meaningfulVersion = (version?: string | null) => {
|
|
|
159
160
|
return parts.map(part => (Number.isNaN(Number.parseInt(part, 10)) ? '0' : part)).join('.');
|
|
160
161
|
};
|
|
161
162
|
|
|
163
|
+
const meaningfulText = (value: unknown): string | null => {
|
|
164
|
+
if (value === undefined || value === null || value === '') return null;
|
|
165
|
+
return String(value);
|
|
166
|
+
};
|
|
167
|
+
|
|
162
168
|
export const mapProtocolV1OnekeyFeaturesToState = (features: OnekeyFeatures): DeviceStatePatch => {
|
|
163
169
|
const verification = definedEntries<DeviceFeaturesVerify>({
|
|
164
170
|
firmwareBuildId: features.onekey_firmware_build_id,
|
|
@@ -203,6 +209,24 @@ export const mapProtocolV1OnekeyFeaturesToState = (features: OnekeyFeatures): De
|
|
|
203
209
|
se04Boot: meaningfulVersion(features.onekey_se04_boot_version),
|
|
204
210
|
}),
|
|
205
211
|
...(Object.keys(verification).length > 0 ? { verification } : {}),
|
|
212
|
+
securityElements: {
|
|
213
|
+
se01: {
|
|
214
|
+
type: meaningfulText(features.onekey_se_type),
|
|
215
|
+
state: meaningfulText(features.onekey_se01_state),
|
|
216
|
+
},
|
|
217
|
+
se02: {
|
|
218
|
+
type: null,
|
|
219
|
+
state: meaningfulText(features.onekey_se02_state),
|
|
220
|
+
},
|
|
221
|
+
se03: {
|
|
222
|
+
type: null,
|
|
223
|
+
state: meaningfulText(features.onekey_se03_state),
|
|
224
|
+
},
|
|
225
|
+
se04: {
|
|
226
|
+
type: null,
|
|
227
|
+
state: meaningfulText(features.onekey_se04_state),
|
|
228
|
+
},
|
|
229
|
+
},
|
|
206
230
|
raw: { protocolV1OneKeyFeatures: features },
|
|
207
231
|
};
|
|
208
232
|
};
|
|
@@ -288,6 +312,24 @@ export const mapProtocolV2DeviceInfoToState = (
|
|
|
288
312
|
se04BootBuildId: imageBuildId(info.se4?.bootloader),
|
|
289
313
|
se04BootHash: imageHash(info.se4?.bootloader),
|
|
290
314
|
}),
|
|
315
|
+
securityElements: {
|
|
316
|
+
se01: {
|
|
317
|
+
type: getProtocolV2SeType(info.se1),
|
|
318
|
+
state: getProtocolV2SeState(info.se1),
|
|
319
|
+
},
|
|
320
|
+
se02: {
|
|
321
|
+
type: getProtocolV2SeType(info.se2),
|
|
322
|
+
state: getProtocolV2SeState(info.se2),
|
|
323
|
+
},
|
|
324
|
+
se03: {
|
|
325
|
+
type: getProtocolV2SeType(info.se3),
|
|
326
|
+
state: getProtocolV2SeState(info.se3),
|
|
327
|
+
},
|
|
328
|
+
se04: {
|
|
329
|
+
type: getProtocolV2SeType(info.se4),
|
|
330
|
+
state: getProtocolV2SeState(info.se4),
|
|
331
|
+
},
|
|
332
|
+
},
|
|
291
333
|
raw: {
|
|
292
334
|
protocolV2DeviceInfo: info,
|
|
293
335
|
...(loader ? { protocolV2DeviceStatus: null } : {}),
|
|
@@ -9,6 +9,62 @@ type StoredDeviceState = DeviceState & {
|
|
|
9
9
|
const getBootloaderMode = (state: StoredDeviceState) =>
|
|
10
10
|
state.status.mode === 'bootloader' || state.status.mode === 'romloader';
|
|
11
11
|
|
|
12
|
+
const projectLegacyAdvancedFields = (state: StoredDeviceState) => {
|
|
13
|
+
const verification = state.verification ?? {};
|
|
14
|
+
const securityElements = state.securityElements ?? {};
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
onekey_device_type: state.identity.deviceType,
|
|
18
|
+
onekey_serial_no: state.identity.serialNo,
|
|
19
|
+
onekey_se_type:
|
|
20
|
+
securityElements.se01?.type ??
|
|
21
|
+
securityElements.se02?.type ??
|
|
22
|
+
securityElements.se03?.type ??
|
|
23
|
+
securityElements.se04?.type,
|
|
24
|
+
onekey_board_version: state.versions.board,
|
|
25
|
+
onekey_board_hash: verification.boardHash,
|
|
26
|
+
onekey_board_build_id: verification.boardBuildId,
|
|
27
|
+
onekey_boot_version: state.versions.bootloader,
|
|
28
|
+
onekey_boot_hash: verification.bootloaderHash,
|
|
29
|
+
onekey_boot_build_id: verification.bootloaderBuildId,
|
|
30
|
+
onekey_firmware_version: state.versions.firmware,
|
|
31
|
+
onekey_firmware_hash: verification.firmwareHash,
|
|
32
|
+
onekey_firmware_build_id: verification.firmwareBuildId,
|
|
33
|
+
onekey_ble_version: state.versions.ble,
|
|
34
|
+
onekey_ble_hash: verification.bleHash,
|
|
35
|
+
onekey_ble_build_id: verification.bleBuildId,
|
|
36
|
+
onekey_ble_name: state.identity.bleName,
|
|
37
|
+
onekey_se01_version: state.versions.se01,
|
|
38
|
+
onekey_se01_hash: verification.se01Hash,
|
|
39
|
+
onekey_se01_build_id: verification.se01BuildId,
|
|
40
|
+
onekey_se01_state: securityElements.se01?.state,
|
|
41
|
+
onekey_se01_boot_version: state.versions.se01Boot,
|
|
42
|
+
onekey_se01_boot_hash: verification.se01BootHash,
|
|
43
|
+
onekey_se01_boot_build_id: verification.se01BootBuildId,
|
|
44
|
+
onekey_se02_version: state.versions.se02,
|
|
45
|
+
onekey_se02_hash: verification.se02Hash,
|
|
46
|
+
onekey_se02_build_id: verification.se02BuildId,
|
|
47
|
+
onekey_se02_state: securityElements.se02?.state,
|
|
48
|
+
onekey_se02_boot_version: state.versions.se02Boot,
|
|
49
|
+
onekey_se02_boot_hash: verification.se02BootHash,
|
|
50
|
+
onekey_se02_boot_build_id: verification.se02BootBuildId,
|
|
51
|
+
onekey_se03_version: state.versions.se03,
|
|
52
|
+
onekey_se03_hash: verification.se03Hash,
|
|
53
|
+
onekey_se03_build_id: verification.se03BuildId,
|
|
54
|
+
onekey_se03_state: securityElements.se03?.state,
|
|
55
|
+
onekey_se03_boot_version: state.versions.se03Boot,
|
|
56
|
+
onekey_se03_boot_hash: verification.se03BootHash,
|
|
57
|
+
onekey_se03_boot_build_id: verification.se03BootBuildId,
|
|
58
|
+
onekey_se04_version: state.versions.se04,
|
|
59
|
+
onekey_se04_hash: verification.se04Hash,
|
|
60
|
+
onekey_se04_build_id: verification.se04BuildId,
|
|
61
|
+
onekey_se04_state: securityElements.se04?.state,
|
|
62
|
+
onekey_se04_boot_version: state.versions.se04Boot,
|
|
63
|
+
onekey_se04_boot_hash: verification.se04BootHash,
|
|
64
|
+
onekey_se04_boot_build_id: verification.se04BootBuildId,
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
12
68
|
export const projectFeatures = (state: StoredDeviceState): Features => {
|
|
13
69
|
const snapshot = cloneDeviceState(state);
|
|
14
70
|
const rawFeatures = snapshot.raw?.protocolV1Features ?? {};
|
|
@@ -21,10 +77,12 @@ export const projectFeatures = (state: StoredDeviceState): Features => {
|
|
|
21
77
|
snapshot.protocol === 'V1'
|
|
22
78
|
? (rawFeatures as { bootloader_mode?: boolean | null }).bootloader_mode ?? null
|
|
23
79
|
: getBootloaderMode(snapshot);
|
|
80
|
+
const legacyAdvancedFields = projectLegacyAdvancedFields(snapshot);
|
|
24
81
|
|
|
25
82
|
return {
|
|
26
83
|
...publicRawFeatures,
|
|
27
84
|
...rawOneKeyFeatures,
|
|
85
|
+
...legacyAdvancedFields,
|
|
28
86
|
protocol: snapshot.protocol,
|
|
29
87
|
protocolVersion: snapshot.protocolVersion ?? (snapshot.protocol === 'V1' ? 1 : null),
|
|
30
88
|
deviceType: snapshot.identity.deviceType,
|
|
@@ -106,6 +106,27 @@ const applySectionPatch = <T extends object>(
|
|
|
106
106
|
}
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
+
const mergeSecurityElements = (
|
|
110
|
+
current: StoredDeviceState['securityElements'],
|
|
111
|
+
patch: NonNullable<DeviceStatePatch['securityElements']>
|
|
112
|
+
) => {
|
|
113
|
+
const merged = cloneDeviceState(current ?? {});
|
|
114
|
+
|
|
115
|
+
for (const key of Object.keys(patch) as Array<keyof typeof patch>) {
|
|
116
|
+
const elementPatch = patch[key];
|
|
117
|
+
if (elementPatch !== undefined) {
|
|
118
|
+
const currentElement = merged[key];
|
|
119
|
+
merged[key] = {
|
|
120
|
+
type: elementPatch.type !== undefined ? elementPatch.type : currentElement?.type ?? null,
|
|
121
|
+
state:
|
|
122
|
+
elementPatch.state !== undefined ? elementPatch.state : currentElement?.state ?? null,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return merged;
|
|
128
|
+
};
|
|
129
|
+
|
|
109
130
|
export class DeviceStateStore {
|
|
110
131
|
private state: StoredDeviceState | undefined;
|
|
111
132
|
|
|
@@ -148,6 +169,16 @@ export class DeviceStateStore {
|
|
|
148
169
|
if (patch.status) applySectionPatch(next.status, patch.status, 'status', changedKeys);
|
|
149
170
|
if (patch.settings) applySectionPatch(next.settings, patch.settings, 'settings', changedKeys);
|
|
150
171
|
if (patch.versions) applySectionPatch(next.versions, patch.versions, 'versions', changedKeys);
|
|
172
|
+
if (patch.securityElements !== undefined) {
|
|
173
|
+
const mergedSecurityElements = mergeSecurityElements(
|
|
174
|
+
next.securityElements,
|
|
175
|
+
patch.securityElements
|
|
176
|
+
);
|
|
177
|
+
if (!isEqual(next.securityElements, mergedSecurityElements)) {
|
|
178
|
+
next.securityElements = mergedSecurityElements;
|
|
179
|
+
changedKeys.push('securityElements');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
151
182
|
|
|
152
183
|
if (patch.capabilities !== undefined && !isEqual(next.capabilities, patch.capabilities)) {
|
|
153
184
|
next.capabilities = cloneDeviceState(patch.capabilities);
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,8 @@ export * from './types';
|
|
|
19
19
|
export { whitelist, whitelistExtension } from './data/config';
|
|
20
20
|
export { executeCallback, cleanupCallback };
|
|
21
21
|
export { preloadSessionCache } from './device/Device';
|
|
22
|
+
export { projectFeatures as projectDeviceStateFeatures } from './device/DeviceStateProjector';
|
|
23
|
+
export { getMethodSupportedProtocols } from './api/utils';
|
|
22
24
|
export {
|
|
23
25
|
getFirmwareUpdateHostBindingGeneration,
|
|
24
26
|
registerFirmwareUpdateHostBinding,
|
package/src/types/device.ts
CHANGED
|
@@ -259,6 +259,19 @@ export type DeviceStateVersions = {
|
|
|
259
259
|
se04Boot?: string | null;
|
|
260
260
|
};
|
|
261
261
|
|
|
262
|
+
export type DeviceStateSecurityElement = {
|
|
263
|
+
type: string | null;
|
|
264
|
+
state: string | null;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
export type DeviceStateSecurityElements = Partial<
|
|
268
|
+
Record<'se01' | 'se02' | 'se03' | 'se04', DeviceStateSecurityElement>
|
|
269
|
+
>;
|
|
270
|
+
|
|
271
|
+
export type DeviceStateSecurityElementsPatch = Partial<
|
|
272
|
+
Record<keyof DeviceStateSecurityElements, Partial<DeviceStateSecurityElement>>
|
|
273
|
+
>;
|
|
274
|
+
|
|
262
275
|
export type DeviceState = {
|
|
263
276
|
schemaVersion: 1;
|
|
264
277
|
revision: number;
|
|
@@ -270,6 +283,8 @@ export type DeviceState = {
|
|
|
270
283
|
status: DeviceStateStatus;
|
|
271
284
|
settings: DeviceStateSettings;
|
|
272
285
|
versions: DeviceStateVersions;
|
|
286
|
+
/** Secure-element metadata exposed by the unified firmware information read. */
|
|
287
|
+
securityElements?: DeviceStateSecurityElements;
|
|
273
288
|
capabilities: Array<number | string>;
|
|
274
289
|
verification?: Partial<DeviceFeaturesVerify>;
|
|
275
290
|
};
|
|
@@ -281,6 +296,7 @@ export type DeviceStatePatch = {
|
|
|
281
296
|
status?: Partial<DeviceStateStatus>;
|
|
282
297
|
settings?: Partial<DeviceStateSettings>;
|
|
283
298
|
versions?: Partial<DeviceStateVersions>;
|
|
299
|
+
securityElements?: DeviceStateSecurityElementsPatch;
|
|
284
300
|
capabilities?: Array<number | string>;
|
|
285
301
|
verification?: DeviceFeaturesVerify;
|
|
286
302
|
raw?: DeviceFeaturesRawPatch;
|
package/src/utils/homescreen.ts
CHANGED
|
@@ -328,6 +328,10 @@ export const getNftSize = ({
|
|
|
328
328
|
full: { width: PRO2_NFT_IMAGE_WIDTH, height: PRO2_NFT_IMAGE_HEIGHT },
|
|
329
329
|
thumbnail: { width: PRO2_NFT_THUMBNAIL_WIDTH, height: PRO2_NFT_THUMBNAIL_HEIGHT },
|
|
330
330
|
},
|
|
331
|
+
neo: {
|
|
332
|
+
full: { width: PRO2_NFT_IMAGE_WIDTH, height: PRO2_NFT_IMAGE_HEIGHT },
|
|
333
|
+
thumbnail: { width: PRO2_NFT_THUMBNAIL_WIDTH, height: PRO2_NFT_THUMBNAIL_HEIGHT },
|
|
334
|
+
},
|
|
331
335
|
};
|
|
332
336
|
|
|
333
337
|
return sizes[deviceType]?.[thumbnail ? 'thumbnail' : 'full'];
|
|
@@ -342,7 +346,7 @@ export const getHomeScreenSize = ({
|
|
|
342
346
|
homeScreenType: 'WallPaper' | 'Nft';
|
|
343
347
|
thumbnail?: boolean;
|
|
344
348
|
}) => {
|
|
345
|
-
if (deviceType === EDeviceType.Pro2) {
|
|
349
|
+
if (deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo) {
|
|
346
350
|
return thumbnail ? undefined : { width: PRO2_WALLPAPER_WIDTH, height: PRO2_WALLPAPER_HEIGHT };
|
|
347
351
|
}
|
|
348
352
|
|