@onekeyfe/hd-core 1.2.0-alpha.36 → 1.2.0-alpha.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +5 -1
- package/__tests__/deviceUploadNft.test.ts +187 -0
- package/__tests__/homescreen.test.ts +41 -1
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/open-wallet-session.test.ts +161 -44
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +86 -35
- package/__tests__/protocol-v2.test.ts +168 -444
- package/__tests__/public-pro2-api-boundary.test.ts +1 -0
- package/__tests__/uiProgressThrottle.test.ts +74 -0
- package/dist/api/BaseMethod.d.ts +3 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +2 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +30 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +34 -2
- package/dist/index.js +544 -496
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +2 -1
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -0
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/utils/homescreen.d.ts +4 -0
- package/dist/utils/homescreen.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- 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/pro2Nft.d.ts +29 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -0
- package/dist/utils/pro2Wallpaper.d.ts +10 -0
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/dist/utils/uiProgressThrottle.d.ts +3 -0
- package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +7 -9
- package/src/api/OpenWalletSession.ts +37 -15
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +29 -30
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +0 -7
- package/src/api/helpers/protocolV2FileWrite.ts +11 -4
- package/src/api/index.ts +1 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +166 -0
- package/src/core/index.ts +83 -82
- package/src/data/messages/messages-protocol-v2.json +10 -15
- package/src/events/logBlockEvent.ts +1 -0
- package/src/inject.ts +1 -0
- package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +117 -0
- package/src/protocols/protocol-v2/walletSession.ts +18 -51
- package/src/types/api/index.ts +2 -0
- package/src/types/api/protocolV2.ts +13 -0
- package/src/utils/homescreen.ts +32 -6
- package/src/utils/index.ts +6 -1
- package/src/utils/pro2Nft.ts +91 -0
- package/src/utils/pro2Wallpaper.ts +35 -8
- package/src/utils/uiProgressThrottle.ts +63 -0
- package/dist/api/firmware/progressThrottle.d.ts +0 -3
- package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
- package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
- package/src/api/firmware/progressThrottle.ts +0 -44
- package/src/protocols/protocol-v2/lockedError.ts +0 -10
- package/src/protocols/protocol-v2/unlockRetry.ts +0 -131
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pro2Nft.d.ts","sourceRoot":"","sources":["../../src/utils/pro2Nft.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,oBAAoB,MAAM,CAAC;AACxC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAC9C,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAC3C,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAClD,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,GAAG,WAAW,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,UAAU,CAAC;IACtB,QAAQ,EAAE,UAAU,CAAC;CACtB,CAAC;AAsBF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,YAAY,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,aAAa,CAkChB"}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
export declare const PRO2_WALLPAPER_WIDTH = 604;
|
|
2
2
|
export declare const PRO2_WALLPAPER_HEIGHT = 1024;
|
|
3
3
|
export type Pro2WallpaperColorFormat = 'RGB565' | 'RGB565A8';
|
|
4
|
+
export type Pro2ImageAlphaMode = 'preserve' | 'black-background';
|
|
5
|
+
export declare function encodePro2Image(options: {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
rgba: Uint8Array | ArrayBuffer;
|
|
9
|
+
alphaMode?: Pro2ImageAlphaMode;
|
|
10
|
+
}): {
|
|
11
|
+
data: Uint8Array;
|
|
12
|
+
colorFormat: Pro2WallpaperColorFormat;
|
|
13
|
+
};
|
|
4
14
|
export declare function encodePro2Wallpaper(options: {
|
|
5
15
|
width: number;
|
|
6
16
|
height: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pro2Wallpaper.d.ts","sourceRoot":"","sources":["../../src/utils/pro2Wallpaper.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,oBAAoB,MAAM,CAAC;AACxC,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,MAAM,MAAM,wBAAwB,GAAG,QAAQ,GAAG,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"pro2Wallpaper.d.ts","sourceRoot":"","sources":["../../src/utils/pro2Wallpaper.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,oBAAoB,MAAM,CAAC;AACxC,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,MAAM,MAAM,wBAAwB,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,kBAAkB,CAAC;AA8BjE,wBAAgB,eAAe,CAAC,OAAO,EAAE;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,GAAG,WAAW,CAAC;IAC/B,SAAS,CAAC,EAAE,kBAAkB,CAAC;CAChC,GAAG;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,wBAAwB,CAAA;CAAE,CA4E9D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,GAAG,WAAW,CAAC;CAChC,GAAG;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,wBAAwB,CAAA;CAAE,CAE9D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uiProgressThrottle.d.ts","sourceRoot":"","sources":["../../src/utils/uiProgressThrottle.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAiB7C,eAAO,MAAM,6BAA6B,yDAMvB,WAAW,YAqC7B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-core",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.39",
|
|
4
4
|
"description": "Core processes and APIs for communicating with OneKey hardware devices.",
|
|
5
5
|
"author": "OneKey",
|
|
6
6
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@onekeyfe/hd-shared": "1.2.0-alpha.
|
|
29
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
28
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.39",
|
|
29
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.39",
|
|
30
30
|
"axios": "1.15.2",
|
|
31
31
|
"bignumber.js": "^9.0.2",
|
|
32
32
|
"bytebuffer": "^5.0.1",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"@types/w3c-web-usb": "^1.0.10",
|
|
45
45
|
"@types/web-bluetooth": "^0.0.21"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "2cb8f692689327fc7e77cb848223cbeaa740dcad"
|
|
48
48
|
}
|
package/src/api/BaseMethod.ts
CHANGED
|
@@ -16,16 +16,14 @@ import { getBleFirmwareReleaseInfo, getFirmwareReleaseInfo } from './firmware/re
|
|
|
16
16
|
import { LoggerNames, getLogger } from '../utils';
|
|
17
17
|
import { generateInstanceId } from '../utils/tracing';
|
|
18
18
|
import { DeviceModelToTypes } from '../types';
|
|
19
|
-
import {
|
|
20
|
-
type UnlockPolicy,
|
|
21
|
-
getProtocolV2UnlockPolicy,
|
|
22
|
-
} from '../protocols/protocol-v2/unlockPolicy';
|
|
23
19
|
|
|
24
20
|
import type { Device } from '../device/Device';
|
|
25
21
|
import type DeviceConnector from '../device/DeviceConnector';
|
|
26
22
|
import type { DeviceFirmwareRange, KnownDevice } from '../types';
|
|
27
23
|
import type { CoreMessage } from '../events';
|
|
24
|
+
import type { UnlockPolicy } from '../protocols/protocol-v2/unlockPolicy';
|
|
28
25
|
import type { ProtocolV2InteractionDescriptor } from '../protocols/protocol-v2/uiInteraction';
|
|
26
|
+
import type { ProtocolV2UnlockContext } from '../protocols/protocol-v2/unlockPolicyRunner';
|
|
29
27
|
import type { RequestContext } from '../utils/tracing';
|
|
30
28
|
import type { CoreContext } from '../core';
|
|
31
29
|
|
|
@@ -128,9 +126,7 @@ export abstract class BaseMethod<Params = undefined> {
|
|
|
128
126
|
*/
|
|
129
127
|
checkDeviceId = false;
|
|
130
128
|
|
|
131
|
-
/**
|
|
132
|
-
* 该方法是否需要校验 passphrase state
|
|
133
|
-
*/
|
|
129
|
+
/** Whether the method requires wallet-session selection and Protocol V2 pre-unlock. */
|
|
134
130
|
useDevicePassphraseState = true;
|
|
135
131
|
|
|
136
132
|
/**
|
|
@@ -168,9 +164,12 @@ export abstract class BaseMethod<Params = undefined> {
|
|
|
168
164
|
*/
|
|
169
165
|
strictCheckDeviceSupport = false;
|
|
170
166
|
|
|
171
|
-
/**
|
|
167
|
+
/** Non-wallet methods may explicitly require Protocol V2 pre-unlock. */
|
|
172
168
|
unlockPolicy: UnlockPolicy = 'none';
|
|
173
169
|
|
|
170
|
+
/** Shared by composite methods so nested calls do not repeat the root preflight. */
|
|
171
|
+
protocolV2UnlockContext?: ProtocolV2UnlockContext;
|
|
172
|
+
|
|
174
173
|
getSupportedProtocols(): readonly ProtocolType[] {
|
|
175
174
|
return ['V1'];
|
|
176
175
|
}
|
|
@@ -217,7 +216,6 @@ export abstract class BaseMethod<Params = undefined> {
|
|
|
217
216
|
this.useDevice = true;
|
|
218
217
|
this.allowDeviceMode = [UI_REQUEST.NOT_INITIALIZE];
|
|
219
218
|
this.requireDeviceMode = [];
|
|
220
|
-
this.unlockPolicy = getProtocolV2UnlockPolicy(this.name);
|
|
221
219
|
}
|
|
222
220
|
|
|
223
221
|
abstract init(): void;
|
|
@@ -91,28 +91,40 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
|
|
|
91
91
|
|
|
92
92
|
async run(): Promise<OpenWalletSessionPayload> {
|
|
93
93
|
const isProtocolV2 = this.device.isProtocolV2();
|
|
94
|
-
|
|
94
|
+
let state = await this.device.getDeviceState({ refreshSections: ['status'] });
|
|
95
95
|
let currentDeviceId = state.identity.deviceId;
|
|
96
|
+
const hasAuthoritativeProtocolV2WalletStatus = (candidate: typeof state) =>
|
|
97
|
+
candidate.status.unlocked === true &&
|
|
98
|
+
typeof candidate.status.passphraseProtection === 'boolean' &&
|
|
99
|
+
typeof candidate.status.unlockedAttachPin === 'boolean';
|
|
100
|
+
const requireAuthoritativeProtocolV2WalletStatus = (candidate: typeof state) => {
|
|
101
|
+
if (!hasAuthoritativeProtocolV2WalletStatus(candidate)) {
|
|
102
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceInitializeFailed);
|
|
103
|
+
}
|
|
104
|
+
return candidate;
|
|
105
|
+
};
|
|
96
106
|
const requireDeviceId = () => {
|
|
97
107
|
if (!currentDeviceId) {
|
|
98
108
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceInitializeFailed);
|
|
99
109
|
}
|
|
100
110
|
return currentDeviceId;
|
|
101
111
|
};
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
currentDeviceId =
|
|
105
|
-
return
|
|
112
|
+
const refreshProtocolV2DeviceState = async () => {
|
|
113
|
+
state = await this.device.getDeviceState({ refreshSections: ['status'] });
|
|
114
|
+
currentDeviceId = state.identity.deviceId;
|
|
115
|
+
return state;
|
|
106
116
|
};
|
|
107
|
-
const
|
|
108
|
-
if (isProtocolV2 && state
|
|
117
|
+
const ensureProtocolV2WalletStatus = async () => {
|
|
118
|
+
if (isProtocolV2 && !hasAuthoritativeProtocolV2WalletStatus(state)) {
|
|
109
119
|
await this.device.unlockDevice(DeviceSessionPinType.Main, {
|
|
110
120
|
source: 'unlock-coordinator',
|
|
111
121
|
reason: 'device-locked',
|
|
112
122
|
deviceOnly: true,
|
|
113
123
|
method: 'openWalletSession',
|
|
114
124
|
});
|
|
125
|
+
requireAuthoritativeProtocolV2WalletStatus(await refreshProtocolV2DeviceState());
|
|
115
126
|
}
|
|
127
|
+
return state;
|
|
116
128
|
};
|
|
117
129
|
|
|
118
130
|
const protocol = isProtocolV2 ? 'V2' : 'V1';
|
|
@@ -124,14 +136,21 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
|
|
|
124
136
|
onlyMainPin: true,
|
|
125
137
|
deriveCardano: this.payload.deriveCardano,
|
|
126
138
|
selectMainWalletBeforeRestore:
|
|
127
|
-
state
|
|
139
|
+
!hasAuthoritativeProtocolV2WalletStatus(state) ||
|
|
140
|
+
state.status.unlockedAttachPin === true,
|
|
128
141
|
})
|
|
129
142
|
: await getPassphraseStateWithRefreshDeviceInfo(this.device, {
|
|
130
143
|
onlyMainPin: true,
|
|
131
144
|
initSession: this.payload.initSession,
|
|
132
145
|
});
|
|
133
|
-
const
|
|
134
|
-
|
|
146
|
+
const refreshedState = isProtocolV2
|
|
147
|
+
? requireAuthoritativeProtocolV2WalletStatus(await refreshProtocolV2DeviceState())
|
|
148
|
+
: state;
|
|
149
|
+
const deviceId = requireDeviceId();
|
|
150
|
+
if (
|
|
151
|
+
session.unlockedAttachPin ||
|
|
152
|
+
(isProtocolV2 && refreshedState.status.unlockedAttachPin === true)
|
|
153
|
+
) {
|
|
135
154
|
try {
|
|
136
155
|
await this.device.lockDevice();
|
|
137
156
|
} catch {
|
|
@@ -151,8 +170,8 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
|
|
|
151
170
|
|
|
152
171
|
if (this.params.mode === OpenWalletSessionMode.ResumeHidden) {
|
|
153
172
|
if (isProtocolV2) {
|
|
154
|
-
await
|
|
155
|
-
const refreshedDeviceId =
|
|
173
|
+
await ensureProtocolV2WalletStatus();
|
|
174
|
+
const refreshedDeviceId = requireDeviceId();
|
|
156
175
|
if (refreshedDeviceId !== this.params.deviceId) {
|
|
157
176
|
deviceWalletSessionStore.delete(this.params.deviceId, this.params.passphraseState);
|
|
158
177
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
|
|
@@ -197,15 +216,18 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
|
|
|
197
216
|
}
|
|
198
217
|
|
|
199
218
|
this.device.passphraseState = undefined;
|
|
200
|
-
await
|
|
219
|
+
await ensureProtocolV2WalletStatus();
|
|
201
220
|
const session = isProtocolV2
|
|
202
221
|
? await getProtocolV2WalletSession(this.device, {
|
|
203
222
|
forceWalletSelection: true,
|
|
204
223
|
deriveCardano: this.payload.deriveCardano,
|
|
205
224
|
})
|
|
206
225
|
: await getPassphraseStateWithRefreshDeviceInfo(this.device, { initSession: true });
|
|
207
|
-
const
|
|
208
|
-
|
|
226
|
+
const refreshedState = isProtocolV2
|
|
227
|
+
? requireAuthoritativeProtocolV2WalletStatus(await refreshProtocolV2DeviceState())
|
|
228
|
+
: state;
|
|
229
|
+
const deviceId = requireDeviceId();
|
|
230
|
+
if (isProtocolV2 && refreshedState.status.passphraseProtection !== true) {
|
|
209
231
|
this.device.clearInternalState();
|
|
210
232
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotOpenedPassphrase);
|
|
211
233
|
}
|
|
@@ -14,8 +14,7 @@ import { findMethod } from '../utils';
|
|
|
14
14
|
import { DEVICE, IFRAME, createUiMessage } from '../../events';
|
|
15
15
|
import { UI_REQUEST } from '../../constants/ui-request';
|
|
16
16
|
import { onDeviceButtonHandler } from '../../core';
|
|
17
|
-
import {
|
|
18
|
-
import { ensureProtocolV2WalletSessionUnlocked } from '../../protocols/protocol-v2/walletSession';
|
|
17
|
+
import { runMethodWithUnlockPolicy } from '../../protocols/protocol-v2/unlockPolicyRunner';
|
|
19
18
|
import {
|
|
20
19
|
completeRequestContext,
|
|
21
20
|
createRequestContext,
|
|
@@ -377,34 +376,34 @@ export default abstract class AllNetworkGetAddressBase extends BaseMethod<
|
|
|
377
376
|
this.device.on(DEVICE.PASSPHRASE, onSignalAbort);
|
|
378
377
|
|
|
379
378
|
preCheckDeviceSupport(this.device, method);
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
this.payload.passphraseState
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
379
|
+
const response = await runMethodWithUnlockPolicy<any[]>(method, this.device, {
|
|
380
|
+
context: this.protocolV2UnlockContext,
|
|
381
|
+
prepare: async () => {
|
|
382
|
+
if (this.temporarySafetyCheckPrompted) {
|
|
383
|
+
method.temporarySafetyCheckPrompted = true;
|
|
384
|
+
} else {
|
|
385
|
+
const appliedTemporarySafetyCheck = await method.checkSafetyLevelOnTestNet();
|
|
386
|
+
if (appliedTemporarySafetyCheck) {
|
|
387
|
+
this.temporarySafetyCheckPrompted = true;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Protocol V2 hands a wallet session to exactly one blockchain request.
|
|
392
|
+
// The parent all-network call consumes its first handoff while fetching
|
|
393
|
+
// the root fingerprint, so each nested chain method must resume the
|
|
394
|
+
// requested hidden wallet before it sends its own device command.
|
|
395
|
+
if (this.device.isProtocolV2() && this.payload.passphraseState) {
|
|
396
|
+
const passphraseStateSafety = await this.device.checkPassphraseStateSafety(
|
|
397
|
+
this.payload.passphraseState,
|
|
398
|
+
false,
|
|
399
|
+
this.payload.skipPassphraseCheck
|
|
400
|
+
);
|
|
401
|
+
if (!passphraseStateSafety) {
|
|
402
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
});
|
|
408
407
|
|
|
409
408
|
if (!Array.isArray(response) || response.length === 0) {
|
|
410
409
|
throw new Error('No response');
|
|
@@ -13,7 +13,6 @@ import { DeviceModelToTypes } from '../../types';
|
|
|
13
13
|
import { DataManager } from '../../data-manager';
|
|
14
14
|
import { BaseMethod } from '../BaseMethod';
|
|
15
15
|
import { DEVICE } from '../../events';
|
|
16
|
-
import { createFirmwareProgressThrottle } from './progressThrottle';
|
|
17
16
|
|
|
18
17
|
import type {
|
|
19
18
|
IFirmwareUpdateProgressType,
|
|
@@ -41,8 +40,6 @@ const isDeviceDisconnectedError = (error: unknown) => {
|
|
|
41
40
|
export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
|
|
42
41
|
checkPromise: Deferred<any> | null = null;
|
|
43
42
|
|
|
44
|
-
private shouldPostFirmwareProgress = createFirmwareProgressThrottle();
|
|
45
|
-
|
|
46
43
|
init(): void {}
|
|
47
44
|
|
|
48
45
|
run(): Promise<any> {
|
|
@@ -86,10 +83,6 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
|
|
|
86
83
|
* @param progress Post the percentage of the progress
|
|
87
84
|
*/
|
|
88
85
|
postProgressMessage = (progress: number, progressType: IFirmwareUpdateProgressType) => {
|
|
89
|
-
if (!this.shouldPostFirmwareProgress(progress, progressType)) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
86
|
this.postMessage(
|
|
94
87
|
createUiMessage(UI_REQUEST.FIRMWARE_PROGRESS, {
|
|
95
88
|
device: this.device.toMessageObject() as KnownDevice,
|
|
@@ -31,19 +31,21 @@ export type ProtocolV2FileWriteOptions = {
|
|
|
31
31
|
uiPercentage?: number;
|
|
32
32
|
timeoutMs?: number;
|
|
33
33
|
maxChunkRetries?: number;
|
|
34
|
+
paceMs?: number;
|
|
34
35
|
throwIfAborted?: () => void;
|
|
35
36
|
onProgress?: (progress: ProtocolV2FileWriteProgress) => void;
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
const MIN_FILE_CHUNK_SIZE = 64;
|
|
39
40
|
|
|
40
|
-
function
|
|
41
|
+
export function isProtocolV2ResponseTimeout(error: unknown) {
|
|
41
42
|
if (!error || typeof error !== 'object') return false;
|
|
42
|
-
const candidate = error as { errorCode?: number; code?: number; message?: string };
|
|
43
|
+
const candidate = error as { errorCode?: number; code?: number | string; message?: string };
|
|
43
44
|
const code = candidate.errorCode ?? candidate.code;
|
|
44
45
|
return (
|
|
45
46
|
code === HardwareErrorCode.BleTimeoutError ||
|
|
46
|
-
|
|
47
|
+
code === 'response-timeout' ||
|
|
48
|
+
/(?:BLE|Lowlevel|Protocol V2) response timeout/i.test(candidate.message ?? '')
|
|
47
49
|
);
|
|
48
50
|
}
|
|
49
51
|
|
|
@@ -147,7 +149,7 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
|
|
|
147
149
|
);
|
|
148
150
|
isWritePending = false;
|
|
149
151
|
} catch (error) {
|
|
150
|
-
if (retryCount >= maxChunkRetries || !
|
|
152
|
+
if (retryCount >= maxChunkRetries || !isProtocolV2ResponseTimeout(error)) throw error;
|
|
151
153
|
retryCount += 1;
|
|
152
154
|
options.throwIfAborted?.();
|
|
153
155
|
}
|
|
@@ -186,6 +188,11 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
|
|
|
186
188
|
elapsedMs > 0 ? Math.round((transferredBytes / elapsedMs) * 1000) : undefined,
|
|
187
189
|
elapsedMs,
|
|
188
190
|
});
|
|
191
|
+
if (options.paceMs && options.paceMs > 0) {
|
|
192
|
+
await new Promise(resolve => {
|
|
193
|
+
setTimeout(resolve, options.paceMs);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
189
196
|
}
|
|
190
197
|
|
|
191
198
|
return {
|
package/src/api/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ export { default as promptWebDeviceAccess } from './PromptWebDeviceAccess';
|
|
|
49
49
|
export { default as deviceReboot } from './protocol-v2/DeviceReboot';
|
|
50
50
|
export { default as deviceGetOnboardingStatus } from './protocol-v2/DeviceGetOnboardingStatus';
|
|
51
51
|
export { default as deviceUploadWallpaper } from './protocol-v2/DeviceUploadWallpaper';
|
|
52
|
+
export { default as deviceUploadNft } from './protocol-v2/DeviceUploadNft';
|
|
52
53
|
export { default as uploadPortfolio } from './UploadPortfolio';
|
|
53
54
|
|
|
54
55
|
export { default as cipherKeyValue } from './CipherKeyValue';
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { createDeviceNotSupportMethodError } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
3
|
+
import { UI_REQUEST, createUiMessage } from '../../events/ui-request';
|
|
4
|
+
import { supportsProtocolV2Message } from '../../protocols/protocol-v2/features';
|
|
5
|
+
import {
|
|
6
|
+
PRO2_NFT_DEFAULT_CHUNK_SIZE,
|
|
7
|
+
PRO2_NFT_DEFAULT_PACE_MS,
|
|
8
|
+
PRO2_NFT_DEFAULT_TIMEOUT_MS,
|
|
9
|
+
PRO2_NFT_DIRECTORY,
|
|
10
|
+
PRO2_NFT_MAX_CHUNK_SIZE,
|
|
11
|
+
PRO2_NFT_MIN_CHUNK_SIZE,
|
|
12
|
+
type Pro2NftBundle,
|
|
13
|
+
type Pro2NftImage,
|
|
14
|
+
buildPro2NftBundle,
|
|
15
|
+
} from '../../utils/pro2Nft';
|
|
16
|
+
import { BaseMethod } from '../BaseMethod';
|
|
17
|
+
import { invalidParameter } from '../helpers/filesystemValidation';
|
|
18
|
+
import { isProtocolV2ResponseTimeout, writeProtocolV2File } from '../helpers/protocolV2FileWrite';
|
|
19
|
+
|
|
20
|
+
export type DeviceUploadNftParams = {
|
|
21
|
+
image: Pro2NftImage;
|
|
22
|
+
thumbnail: Pro2NftImage;
|
|
23
|
+
title: string;
|
|
24
|
+
subtitle: string;
|
|
25
|
+
timestampMs?: number;
|
|
26
|
+
chunkSize?: number;
|
|
27
|
+
paceMs?: number;
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type DeviceUploadNftResponse = {
|
|
32
|
+
basename: string;
|
|
33
|
+
imagePath: string;
|
|
34
|
+
thumbnailPath: string;
|
|
35
|
+
metadataPath: string;
|
|
36
|
+
totalSize: number;
|
|
37
|
+
nftUpdated: true;
|
|
38
|
+
message?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
|
|
42
|
+
const NFT_UPDATE_MESSAGE_TYPE = 61500;
|
|
43
|
+
|
|
44
|
+
export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
|
|
45
|
+
private bundle?: Pro2NftBundle;
|
|
46
|
+
|
|
47
|
+
getSupportedProtocols() {
|
|
48
|
+
return ['V2'] as const;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
init() {
|
|
52
|
+
const {
|
|
53
|
+
image,
|
|
54
|
+
thumbnail,
|
|
55
|
+
title,
|
|
56
|
+
subtitle,
|
|
57
|
+
timestampMs = Date.now(),
|
|
58
|
+
chunkSize = PRO2_NFT_DEFAULT_CHUNK_SIZE,
|
|
59
|
+
paceMs = PRO2_NFT_DEFAULT_PACE_MS,
|
|
60
|
+
timeoutMs = PRO2_NFT_DEFAULT_TIMEOUT_MS,
|
|
61
|
+
} = this.payload;
|
|
62
|
+
if (
|
|
63
|
+
!Number.isInteger(chunkSize) ||
|
|
64
|
+
chunkSize < PRO2_NFT_MIN_CHUNK_SIZE ||
|
|
65
|
+
chunkSize > PRO2_NFT_MAX_CHUNK_SIZE
|
|
66
|
+
) {
|
|
67
|
+
throw invalidParameter(
|
|
68
|
+
`Parameter [chunkSize] must be an integer between ${PRO2_NFT_MIN_CHUNK_SIZE} and ${PRO2_NFT_MAX_CHUNK_SIZE}.`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (!Number.isInteger(paceMs) || paceMs < 0) {
|
|
72
|
+
throw invalidParameter('Parameter [paceMs] must be a non-negative integer.');
|
|
73
|
+
}
|
|
74
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
75
|
+
throw invalidParameter('Parameter [timeoutMs] must be a positive integer.');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.bundle = buildPro2NftBundle({ image, thumbnail, title, subtitle, timestampMs });
|
|
79
|
+
this.params = { image, thumbnail, title, subtitle, timestampMs, chunkSize, paceMs, timeoutMs };
|
|
80
|
+
this.unlockPolicy = 'none';
|
|
81
|
+
this.skipForceUpdateCheck = true;
|
|
82
|
+
this.useDevicePassphraseState = false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private async assertCapabilities() {
|
|
86
|
+
const protocolInfo = await this.device.ensureProtocolV2RuntimeContext();
|
|
87
|
+
const hasFileWrite = supportsProtocolV2Message(
|
|
88
|
+
protocolInfo,
|
|
89
|
+
FILESYSTEM_FILE_WRITE_MESSAGE_TYPE
|
|
90
|
+
);
|
|
91
|
+
const hasNftUpdate = supportsProtocolV2Message(protocolInfo, NFT_UPDATE_MESSAGE_TYPE);
|
|
92
|
+
if (!hasFileWrite || !hasNftUpdate) {
|
|
93
|
+
throw createDeviceNotSupportMethodError(this.name, this.device.getCurrentFirmwareType());
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private async updateNft(basename: string) {
|
|
98
|
+
const params = { file_name_no_ext: basename };
|
|
99
|
+
const options = { timeoutMs: this.params.timeoutMs };
|
|
100
|
+
try {
|
|
101
|
+
return await this.device.commands.typedCall('NftUpdate', 'Success', params, options);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (!isProtocolV2ResponseTimeout(error)) {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
this.throwIfAborted();
|
|
107
|
+
return this.device.commands.typedCall('NftUpdate', 'Success', params, options);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async run(): Promise<DeviceUploadNftResponse> {
|
|
112
|
+
const { bundle } = this;
|
|
113
|
+
if (!bundle) throw invalidParameter('NFT data has not been initialized.');
|
|
114
|
+
|
|
115
|
+
await this.assertCapabilities();
|
|
116
|
+
|
|
117
|
+
const files = [
|
|
118
|
+
{ path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.bin`, data: bundle.image },
|
|
119
|
+
{ path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}_m.bin`, data: bundle.thumbnail },
|
|
120
|
+
{ path: `${PRO2_NFT_DIRECTORY}/${bundle.basename}.json`, data: bundle.metadata },
|
|
121
|
+
];
|
|
122
|
+
const totalSize = files.reduce((sum, file) => sum + file.data.byteLength, 0);
|
|
123
|
+
let transferredBeforeFile = 0;
|
|
124
|
+
|
|
125
|
+
for (const file of files) {
|
|
126
|
+
const transferredAtFileStart = transferredBeforeFile;
|
|
127
|
+
await writeProtocolV2File({
|
|
128
|
+
commands: this.device.commands,
|
|
129
|
+
path: file.path,
|
|
130
|
+
data: file.data,
|
|
131
|
+
totalSize: file.data.byteLength,
|
|
132
|
+
chunkSize: this.params.chunkSize,
|
|
133
|
+
timeoutMs: this.params.timeoutMs,
|
|
134
|
+
paceMs: this.params.paceMs,
|
|
135
|
+
overwrite: true,
|
|
136
|
+
append: false,
|
|
137
|
+
throwIfAborted: () => this.throwIfAborted(),
|
|
138
|
+
onProgress: progress => {
|
|
139
|
+
if (typeof this.postMessage !== 'function') return;
|
|
140
|
+
const transferredBytes = transferredAtFileStart + progress.transferredBytes;
|
|
141
|
+
this.postMessage(
|
|
142
|
+
createUiMessage(UI_REQUEST.DEVICE_PROGRESS, {
|
|
143
|
+
...progress,
|
|
144
|
+
progress: Math.floor((transferredBytes / totalSize) * 100),
|
|
145
|
+
transferredBytes,
|
|
146
|
+
totalBytes: totalSize,
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
transferredBeforeFile += file.data.byteLength;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.throwIfAborted();
|
|
155
|
+
const response = await this.updateNft(bundle.basename);
|
|
156
|
+
return {
|
|
157
|
+
basename: bundle.basename,
|
|
158
|
+
imagePath: files[0].path,
|
|
159
|
+
thumbnailPath: files[1].path,
|
|
160
|
+
metadataPath: files[2].path,
|
|
161
|
+
totalSize,
|
|
162
|
+
nftUpdated: true,
|
|
163
|
+
message: response.message?.message,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|