@onekeyfe/hd-core 1.2.0-alpha.35 → 1.2.0-alpha.37
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 +78 -0
- package/__tests__/DeviceEventRegistration.test.ts +6 -0
- package/__tests__/deviceUploadNft.test.ts +187 -0
- package/__tests__/get-device-state.test.ts +104 -12
- package/__tests__/homescreen.test.ts +41 -1
- package/__tests__/logBlockEvent.test.ts +1 -1
- package/__tests__/open-wallet-session.test.ts +259 -18
- package/__tests__/pro2Nft.test.ts +108 -0
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +56 -0
- package/__tests__/protocol-v2-unlock-policy.test.ts +86 -35
- package/__tests__/protocol-v2.test.ts +377 -402
- package/__tests__/public-pro2-api-boundary.test.ts +1 -0
- package/dist/api/BaseMethod.d.ts +3 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.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/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/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/core/deviceEventRegistration.d.ts +2 -0
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +22 -4
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/events/device.d.ts +4 -0
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +27 -2
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +88 -7
- package/dist/index.js +937 -604
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
- package/dist/protocols/protocol-v2/uiInteraction.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 +4 -1
- 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/deviceFeaturesUtils.d.ts +2 -0
- package/dist/utils/deviceFeaturesUtils.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/package.json +4 -4
- package/src/api/BaseMethod.ts +8 -10
- package/src/api/FirmwareUpdateV4.ts +13 -6
- package/src/api/GetPassphraseState.ts +7 -1
- package/src/api/OpenWalletSession.ts +54 -17
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +32 -11
- package/src/api/device/DeviceUnlock.ts +1 -1
- 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/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- package/src/core/deviceEventRegistration.ts +4 -0
- package/src/core/index.ts +111 -74
- package/src/data/messages/messages-protocol-v2.json +10 -0
- package/src/device/Device.ts +221 -24
- package/src/events/device.ts +4 -0
- package/src/events/logBlockEvent.ts +1 -0
- package/src/events/ui-request.ts +32 -2
- package/src/inject.ts +1 -0
- package/src/protocols/protocol-v2/features.ts +9 -2
- package/src/protocols/protocol-v2/uiInteraction.ts +31 -5
- 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 +93 -18
- package/src/types/api/index.ts +2 -0
- package/src/types/api/protocolV2.ts +13 -0
- package/src/utils/deviceFeaturesUtils.ts +4 -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/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/protocols/protocol-v2/lockedError.ts +0 -10
- package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
package/src/core/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import semver from 'semver';
|
|
2
2
|
import EventEmitter from 'events';
|
|
3
|
-
import { TRANSPORT_EVENT } from '@onekeyfe/hd-transport';
|
|
3
|
+
import { DeviceSessionPinType, TRANSPORT_EVENT } from '@onekeyfe/hd-transport';
|
|
4
4
|
import {
|
|
5
5
|
ERRORS,
|
|
6
6
|
ERROR_CODES_REQUIRE_RELEASE,
|
|
@@ -55,7 +55,7 @@ import DeviceConnector from '../device/DeviceConnector';
|
|
|
55
55
|
import RequestQueue from './RequestQueue';
|
|
56
56
|
import { registerHardwareUiEventListeners } from './deviceEventRegistration';
|
|
57
57
|
import { getSynchronize } from '../utils/getSynchronize';
|
|
58
|
-
import {
|
|
58
|
+
import { runMethodWithUnlockPolicy } from '../protocols/protocol-v2/unlockPolicyRunner';
|
|
59
59
|
import {
|
|
60
60
|
ProtocolV2UiInteractionCoordinator,
|
|
61
61
|
isProtocolV2UiEnabled,
|
|
@@ -406,6 +406,8 @@ const onCallDevice = async (
|
|
|
406
406
|
|
|
407
407
|
registerHardwareUiEventListeners(device, {
|
|
408
408
|
pin: onDevicePinHandler,
|
|
409
|
+
pinOnDevice: onEnterPinOnDeviceHandler,
|
|
410
|
+
pinOnDeviceComplete: onPinOnDeviceCompleteHandler,
|
|
409
411
|
button: onDeviceButtonHandler,
|
|
410
412
|
passphrase: message.payload.useEmptyPassphrase
|
|
411
413
|
? onEmptyPassphraseHandler
|
|
@@ -421,6 +423,7 @@ const onCallDevice = async (
|
|
|
421
423
|
);
|
|
422
424
|
|
|
423
425
|
const protocolV2UiCoordinator = new ProtocolV2UiInteractionCoordinator(device, postMessage);
|
|
426
|
+
device.beginProtocolV2UiInteraction();
|
|
424
427
|
device.on(
|
|
425
428
|
DEVICE.SELECT_DEVICE_FOR_SWITCH_FIRMWARE_WEB_DEVICE,
|
|
426
429
|
onSelectDeviceForSwitchFirmwareWebDeviceHandler
|
|
@@ -547,89 +550,92 @@ const onCallDevice = async (
|
|
|
547
550
|
);
|
|
548
551
|
}
|
|
549
552
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
553
|
+
try {
|
|
554
|
+
let deviceIdCheckedDuringUnlockPreflight = false;
|
|
555
|
+
const response = await runMethodWithUnlockPolicy<object>(method, device, {
|
|
556
|
+
uiCoordinator: protocolV2UiCoordinator,
|
|
557
|
+
afterStatusBeforeUnlock: () => {
|
|
558
|
+
if (method.deviceId && method.checkDeviceId) {
|
|
559
|
+
if (!device.checkDeviceId(method.deviceId)) {
|
|
560
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
|
|
561
|
+
}
|
|
562
|
+
deviceIdCheckedDuringUnlockPreflight = true;
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
prepare: async () => {
|
|
566
|
+
if (method.deviceId && method.checkDeviceId && !deviceIdCheckedDuringUnlockPreflight) {
|
|
567
|
+
const isSameDeviceID = await checkLiveDeviceId(device, method.deviceId);
|
|
568
|
+
if (!isSameDeviceID) {
|
|
569
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
561
572
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
573
|
+
/**
|
|
574
|
+
* check firmware release info
|
|
575
|
+
*/
|
|
576
|
+
method.checkFirmwareRelease();
|
|
566
577
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
578
|
+
/**
|
|
579
|
+
* check additional supported feature
|
|
580
|
+
*/
|
|
581
|
+
method.checkDeviceSupportFeature();
|
|
571
582
|
|
|
572
|
-
|
|
573
|
-
|
|
583
|
+
// reconfigure messages
|
|
584
|
+
if (_deviceList && device.features && !device.isProtocolV2()) {
|
|
585
|
+
await TransportManager.reconfigure(device.features);
|
|
586
|
+
}
|
|
574
587
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
588
|
+
// Check to see if it is safe to use Passphrase
|
|
589
|
+
checkPassphraseEnableState(method, device.features);
|
|
590
|
+
|
|
591
|
+
if (shouldCheckPassphraseState(method, device)) {
|
|
592
|
+
// check version
|
|
593
|
+
const support = device.supportNewPassphrase();
|
|
594
|
+
if (!support.support) {
|
|
595
|
+
throw ERRORS.TypedError(
|
|
596
|
+
HardwareErrorCode.DeviceNotSupportPassphrase,
|
|
597
|
+
`Device not support passphrase, please update to ${support.require}`,
|
|
598
|
+
{
|
|
599
|
+
require: support.require,
|
|
600
|
+
}
|
|
601
|
+
);
|
|
585
602
|
}
|
|
586
|
-
)
|
|
587
|
-
);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// Check Device passphrase State
|
|
591
|
-
const passphraseStateSafety = await device.checkPassphraseStateSafety(
|
|
592
|
-
method.payload?.passphraseState,
|
|
593
|
-
method.payload?.useEmptyPassphrase,
|
|
594
|
-
method.payload?.skipPassphraseCheck
|
|
595
|
-
);
|
|
596
603
|
|
|
597
|
-
|
|
598
|
-
|
|
604
|
+
// Check Device passphrase State after the Protocol V2 pre-unlock gate.
|
|
605
|
+
const passphraseStateSafety = await device.checkPassphraseStateSafety(
|
|
606
|
+
method.payload?.passphraseState,
|
|
607
|
+
method.payload?.useEmptyPassphrase,
|
|
608
|
+
method.payload?.skipPassphraseCheck,
|
|
609
|
+
hasDeriveCardano(method)
|
|
610
|
+
);
|
|
599
611
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
return Promise.reject(
|
|
603
|
-
ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError)
|
|
604
|
-
);
|
|
605
|
-
}
|
|
612
|
+
// Double check, handles the special case of Touch/Pro
|
|
613
|
+
checkPassphraseEnableState(method, device.features);
|
|
606
614
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
615
|
+
if (!passphraseStateSafety) {
|
|
616
|
+
DevicePool.clearDeviceCache(method.payload.connectId);
|
|
617
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
|
|
618
|
+
}
|
|
610
619
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
? e
|
|
618
|
-
: ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'open safety check failed.');
|
|
619
|
-
// messageResponse = createResponseMessage(method.responseID, false, { error });
|
|
620
|
-
// requestQueue.resolveRequest(method.responseID, messageResponse);
|
|
621
|
-
// return;
|
|
622
|
-
throw error;
|
|
623
|
-
}
|
|
620
|
+
// Protocol V2 emits the PIN phase completion from DeviceSessionAskPin.
|
|
621
|
+
// Keep the legacy compatibility close for Protocol V1 only.
|
|
622
|
+
if (!device.isProtocolV2()) {
|
|
623
|
+
postMessage(createUiMessage(UI_REQUEST.CLOSE_UI_PIN_WINDOW));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
624
626
|
|
|
625
|
-
|
|
627
|
+
// Automatic check safety_check level for Kovan, Ropsten, Rinkeby, Goerli test networks.
|
|
628
|
+
try {
|
|
629
|
+
await method.checkSafetyLevelOnTestNet();
|
|
630
|
+
} catch (e) {
|
|
631
|
+
throw e instanceof HardwareError
|
|
632
|
+
? e
|
|
633
|
+
: ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'open safety check failed.');
|
|
634
|
+
}
|
|
626
635
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
device,
|
|
631
|
-
protocolV2UiCoordinator
|
|
632
|
-
);
|
|
636
|
+
method.device?.commands?.checkDisposed();
|
|
637
|
+
},
|
|
638
|
+
});
|
|
633
639
|
messageResponse = createResponseMessage(method.responseID, true, response);
|
|
634
640
|
requestQueue.resolveRequest(method.responseID, messageResponse);
|
|
635
641
|
completeMethodRequestContext(method);
|
|
@@ -1279,6 +1285,7 @@ const onDevicePassphraseHandler = async (
|
|
|
1279
1285
|
source: requestPayload.source,
|
|
1280
1286
|
reason: requestPayload.reason,
|
|
1281
1287
|
expectedPassphraseState: requestPayload.expectedPassphraseState,
|
|
1288
|
+
...(requestPayload.interaction ? { interaction: requestPayload.interaction } : {}),
|
|
1282
1289
|
})
|
|
1283
1290
|
);
|
|
1284
1291
|
// wait for passphrase
|
|
@@ -1308,6 +1315,7 @@ const onEnterPassphraseOnDeviceHandler = (
|
|
|
1308
1315
|
passphraseState: device.passphraseState,
|
|
1309
1316
|
source: requestPayload?.source,
|
|
1310
1317
|
reason: requestPayload?.reason,
|
|
1318
|
+
...(requestPayload?.interaction ? { interaction: requestPayload.interaction } : {}),
|
|
1311
1319
|
})
|
|
1312
1320
|
);
|
|
1313
1321
|
};
|
|
@@ -1321,10 +1329,39 @@ const onEnterAttachPinOnDeviceHandler = (
|
|
|
1321
1329
|
type: 'ButtonRequest_AttachPin',
|
|
1322
1330
|
source: requestPayload?.source,
|
|
1323
1331
|
reason: requestPayload?.reason,
|
|
1332
|
+
...(requestPayload?.interaction ? { interaction: requestPayload.interaction } : {}),
|
|
1333
|
+
})
|
|
1334
|
+
);
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1337
|
+
const onEnterPinOnDeviceHandler = (
|
|
1338
|
+
...[device, pinType, metadata]: [...DeviceEvents['pin_on_device']]
|
|
1339
|
+
) => {
|
|
1340
|
+
postMessage(
|
|
1341
|
+
createUiMessage(UI_REQUEST.REQUEST_PIN, {
|
|
1342
|
+
device: device.toMessageObject() as KnownDevice,
|
|
1343
|
+
type:
|
|
1344
|
+
pinType === DeviceSessionPinType.AttachToPin
|
|
1345
|
+
? 'ButtonRequest_AttachPin'
|
|
1346
|
+
: 'ButtonRequest_PinEntry',
|
|
1347
|
+
source: metadata?.source,
|
|
1348
|
+
reason: metadata?.reason,
|
|
1349
|
+
deviceOnly: metadata?.deviceOnly ?? true,
|
|
1350
|
+
completion: metadata?.completion,
|
|
1351
|
+
method: metadata?.method,
|
|
1352
|
+
page: metadata?.page,
|
|
1353
|
+
operation: metadata?.operation,
|
|
1354
|
+
...(metadata?.interaction ? { interaction: metadata.interaction } : {}),
|
|
1324
1355
|
})
|
|
1325
1356
|
);
|
|
1326
1357
|
};
|
|
1327
1358
|
|
|
1359
|
+
const onPinOnDeviceCompleteHandler = (
|
|
1360
|
+
...[_, metadata]: [...DeviceEvents['pin_on_device_complete']]
|
|
1361
|
+
) => {
|
|
1362
|
+
postMessage(createUiMessage(UI_REQUEST.CLOSE_UI_PIN_WINDOW, metadata));
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1328
1365
|
const onSelectDeviceInBootloaderForWebDeviceHandler = async (
|
|
1329
1366
|
...[device, callback]: [...DeviceEvents['select_device_in_bootloader_for_web_device']]
|
|
1330
1367
|
) => {
|
|
@@ -470,6 +470,7 @@
|
|
|
470
470
|
"MessageType_DeviceSessionAskPin": 61202,
|
|
471
471
|
"MessageType_DeviceSessionAskPassphrase": 61203,
|
|
472
472
|
"MessageType_PortfolioUpdate": 61400,
|
|
473
|
+
"MessageType_NftUpdate": 61500,
|
|
473
474
|
"MessageType_OnboardingStatusGet": 61600,
|
|
474
475
|
"MessageType_OnboardingStatus": 61601
|
|
475
476
|
},
|
|
@@ -12538,6 +12539,15 @@
|
|
|
12538
12539
|
}
|
|
12539
12540
|
}
|
|
12540
12541
|
},
|
|
12542
|
+
"NftUpdate": {
|
|
12543
|
+
"fields": {
|
|
12544
|
+
"file_name_no_ext": {
|
|
12545
|
+
"rule": "required",
|
|
12546
|
+
"type": "string",
|
|
12547
|
+
"id": 1
|
|
12548
|
+
}
|
|
12549
|
+
}
|
|
12550
|
+
},
|
|
12541
12551
|
"OnboardingStep": {
|
|
12542
12552
|
"values": {
|
|
12543
12553
|
"ONBOARDING_STEP_UNKNOWN": 0,
|
package/src/device/Device.ts
CHANGED
|
@@ -74,7 +74,9 @@ import type { DeviceStateReadOptions } from '../types/api/getDeviceState';
|
|
|
74
74
|
import type {
|
|
75
75
|
DeviceButtonRequestPayload,
|
|
76
76
|
DeviceFeaturesPayload,
|
|
77
|
+
HardwareUiInteractionMeta,
|
|
77
78
|
PassphraseRequestPayload,
|
|
79
|
+
ProtocolV2UiEventMetadata,
|
|
78
80
|
} from '../events';
|
|
79
81
|
import type { PassphrasePromptResponse } from './DeviceCommands';
|
|
80
82
|
import type { Deferred, HardwareConnectProtocol } from '@onekeyfe/hd-shared';
|
|
@@ -110,8 +112,30 @@ const parseRunOptions = (options?: RunOptions): RunOptions => {
|
|
|
110
112
|
|
|
111
113
|
const Log = getLogger(LoggerNames.Device);
|
|
112
114
|
|
|
115
|
+
const isProtocolV2DeviceStatusUnsupportedError = (error: unknown) => {
|
|
116
|
+
if (error instanceof HardwareError) {
|
|
117
|
+
if (error.errorCode === HardwareErrorCode.DeviceNotSupportMethod) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
if (error.params?.failureCode === 'Failure_UnexpectedMessage') {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const message =
|
|
126
|
+
error instanceof Error
|
|
127
|
+
? error.message
|
|
128
|
+
: String((error as { message?: unknown } | null)?.message ?? error ?? '');
|
|
129
|
+
return (
|
|
130
|
+
/^Failure_UnexpectedMessage(?:,|\b)/i.test(message) ||
|
|
131
|
+
/\b(?:unsupported message|handler not registered|message handler not found)\b/i.test(message)
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
113
135
|
export interface DeviceEvents {
|
|
114
136
|
[DEVICE.PIN]: [Device, PROTO.PinMatrixRequestType | undefined, (err: any, pin: string) => void];
|
|
137
|
+
[DEVICE.PIN_ON_DEVICE]: [Device, DeviceSessionPinType, ProtocolV2UiEventMetadata?];
|
|
138
|
+
[DEVICE.PIN_ON_DEVICE_COMPLETE]: [Device, HardwareUiInteractionMeta];
|
|
115
139
|
[DEVICE.PASSPHRASE_ON_DEVICE]: [Device, PassphraseRequestPayload?];
|
|
116
140
|
[DEVICE.ATTACH_PIN_ON_DEVICE]: [Device, PassphraseRequestPayload?];
|
|
117
141
|
[DEVICE.BUTTON]: [Device, DeviceButtonRequestPayload];
|
|
@@ -210,6 +234,24 @@ export class Device extends EventEmitter {
|
|
|
210
234
|
/** Force the next initialization to reload DeviceInfo after reconnect or reboot. */
|
|
211
235
|
private protocolV2StateNeedsReload = false;
|
|
212
236
|
|
|
237
|
+
/** Runtime context negotiated once per active Protocol V2 link. */
|
|
238
|
+
private protocolV2RuntimeContext?: ProtocolInfo;
|
|
239
|
+
|
|
240
|
+
/** Coalesces concurrent first-use runtime negotiation on the same active link. */
|
|
241
|
+
private protocolV2RuntimeContextPromise?: Promise<ProtocolInfo>;
|
|
242
|
+
|
|
243
|
+
/** Invalidates an in-flight runtime-context response without adding a transport epoch. */
|
|
244
|
+
private protocolV2RuntimeContextRequestToken?: object;
|
|
245
|
+
|
|
246
|
+
private protocolV2UiInteraction?: {
|
|
247
|
+
interactionId: string;
|
|
248
|
+
phaseCounter: number;
|
|
249
|
+
sequence: number;
|
|
250
|
+
opened: boolean;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
private protocolV2UiInteractionCounter = 0;
|
|
254
|
+
|
|
213
255
|
get state() {
|
|
214
256
|
return this.stateStore.getState();
|
|
215
257
|
}
|
|
@@ -967,13 +1009,7 @@ export class Device extends EventEmitter {
|
|
|
967
1009
|
}
|
|
968
1010
|
|
|
969
1011
|
if (refresh.has('status') && !initializedWithDeviceInfo) {
|
|
970
|
-
|
|
971
|
-
refreshedDeviceInfo ??
|
|
972
|
-
(await requestProtocolV2DeviceInfo({
|
|
973
|
-
commands: this.commands,
|
|
974
|
-
request: getProtocolV2DeviceInfoRequest(),
|
|
975
|
-
}));
|
|
976
|
-
await this.probeProtocolV2RuntimeState(deviceInfo);
|
|
1012
|
+
await this.probeProtocolV2RuntimeState(refreshedDeviceInfo);
|
|
977
1013
|
}
|
|
978
1014
|
|
|
979
1015
|
if (refresh.has('settings') && this.state?.status.mode === 'normal') {
|
|
@@ -1044,15 +1080,58 @@ export class Device extends EventEmitter {
|
|
|
1044
1080
|
return this.features;
|
|
1045
1081
|
}
|
|
1046
1082
|
|
|
1047
|
-
async
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1083
|
+
async ensureProtocolV2RuntimeContext(timeoutMs?: number): Promise<ProtocolInfo> {
|
|
1084
|
+
const cachedProtocolInfo =
|
|
1085
|
+
this.protocolV2RuntimeContext ??
|
|
1086
|
+
(!this.protocolV2StateNeedsReload
|
|
1087
|
+
? this.state?.raw?.protocolV2ProtocolInfo ?? undefined
|
|
1088
|
+
: undefined);
|
|
1089
|
+
if (cachedProtocolInfo) {
|
|
1090
|
+
this.protocolV2RuntimeContext = cachedProtocolInfo;
|
|
1091
|
+
return cachedProtocolInfo;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
if (this.protocolV2RuntimeContextPromise) {
|
|
1095
|
+
return this.protocolV2RuntimeContextPromise;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const requestToken = {};
|
|
1099
|
+
const pendingRequest = (async () => {
|
|
1100
|
+
const protocolInfo = await requestProtocolV2ProtocolInfo({
|
|
1101
|
+
commands: this.commands,
|
|
1102
|
+
timeoutMs,
|
|
1103
|
+
});
|
|
1104
|
+
if (this.protocolV2RuntimeContextRequestToken !== requestToken) {
|
|
1105
|
+
throw ERRORS.TypedError(
|
|
1106
|
+
HardwareErrorCode.DeviceInitializeFailed,
|
|
1107
|
+
'Protocol V2 runtime context was invalidated while loading.'
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
this.protocolV2RuntimeContext = protocolInfo;
|
|
1111
|
+
return protocolInfo;
|
|
1112
|
+
})();
|
|
1113
|
+
this.protocolV2RuntimeContextRequestToken = requestToken;
|
|
1114
|
+
this.protocolV2RuntimeContextPromise = pendingRequest;
|
|
1115
|
+
|
|
1116
|
+
try {
|
|
1117
|
+
return await pendingRequest;
|
|
1118
|
+
} finally {
|
|
1119
|
+
if (this.protocolV2RuntimeContextPromise === pendingRequest) {
|
|
1120
|
+
this.protocolV2RuntimeContextPromise = undefined;
|
|
1121
|
+
}
|
|
1122
|
+
if (this.protocolV2RuntimeContextRequestToken === requestToken) {
|
|
1123
|
+
this.protocolV2RuntimeContextRequestToken = undefined;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
async probeProtocolV2RuntimeState(deviceInfo?: ProtocolV2DeviceInfo, timeoutMs?: number) {
|
|
1129
|
+
const protocolInfo = await this.ensureProtocolV2RuntimeContext(timeoutMs);
|
|
1052
1130
|
const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1131
|
+
const runtimeDeviceInfo = deviceInfo ?? this.state?.raw?.protocolV2DeviceInfo;
|
|
1132
|
+
const protocolV2DeviceType = runtimeDeviceInfo
|
|
1133
|
+
? resolveProtocolV2DeviceIdentity(runtimeDeviceInfo.hw?.Device_type).deviceType
|
|
1134
|
+
: this.getCurrentDeviceType();
|
|
1056
1135
|
if (runtimeMode === 'romloader' && protocolV2DeviceType !== EDeviceType.Pro2) {
|
|
1057
1136
|
throw ERRORS.TypedError(
|
|
1058
1137
|
HardwareErrorCode.DeviceInitializeFailed,
|
|
@@ -1078,10 +1157,18 @@ export class Device extends EventEmitter {
|
|
|
1078
1157
|
);
|
|
1079
1158
|
}
|
|
1080
1159
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1160
|
+
let deviceStatus: DeviceStatus;
|
|
1161
|
+
try {
|
|
1162
|
+
deviceStatus = await requestProtocolV2DeviceStatus({
|
|
1163
|
+
commands: this.commands,
|
|
1164
|
+
timeoutMs,
|
|
1165
|
+
});
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
if (runtimeMode === undefined && isProtocolV2DeviceStatusUnsupportedError(error)) {
|
|
1168
|
+
return this.updateProtocolV2Features(deviceInfo, null, 'bootloader', protocolInfo);
|
|
1169
|
+
}
|
|
1170
|
+
throw error;
|
|
1171
|
+
}
|
|
1085
1172
|
return this.updateProtocolV2Features(deviceInfo, deviceStatus, 'normal', protocolInfo);
|
|
1086
1173
|
}
|
|
1087
1174
|
|
|
@@ -1119,6 +1206,9 @@ export class Device extends EventEmitter {
|
|
|
1119
1206
|
this.deviceAcquired = false;
|
|
1120
1207
|
if (!this.isProtocolV2()) return;
|
|
1121
1208
|
this.protocolV2StateNeedsReload = true;
|
|
1209
|
+
this.protocolV2RuntimeContext = undefined;
|
|
1210
|
+
this.protocolV2RuntimeContextPromise = undefined;
|
|
1211
|
+
this.protocolV2RuntimeContextRequestToken = undefined;
|
|
1122
1212
|
this.clearPreInitialized();
|
|
1123
1213
|
}
|
|
1124
1214
|
|
|
@@ -1127,9 +1217,14 @@ export class Device extends EventEmitter {
|
|
|
1127
1217
|
if (deviceId) {
|
|
1128
1218
|
deviceWalletSessionStore.deleteDevice(deviceId);
|
|
1129
1219
|
}
|
|
1130
|
-
if (this.isProtocolV2()
|
|
1131
|
-
|
|
1220
|
+
if (this.isProtocolV2()) {
|
|
1221
|
+
if (this.originalDescriptor.path !== deviceId) {
|
|
1222
|
+
deviceWalletSessionStore.deleteDevice(this.originalDescriptor.path);
|
|
1223
|
+
}
|
|
1132
1224
|
this.protocolV2StateNeedsReload = true;
|
|
1225
|
+
this.protocolV2RuntimeContext = undefined;
|
|
1226
|
+
this.protocolV2RuntimeContextPromise = undefined;
|
|
1227
|
+
this.protocolV2RuntimeContextRequestToken = undefined;
|
|
1133
1228
|
}
|
|
1134
1229
|
|
|
1135
1230
|
this.passphraseState = undefined;
|
|
@@ -1142,6 +1237,9 @@ export class Device extends EventEmitter {
|
|
|
1142
1237
|
if (!this.isProtocolV2()) return;
|
|
1143
1238
|
|
|
1144
1239
|
this.protocolV2StateNeedsReload = true;
|
|
1240
|
+
this.protocolV2RuntimeContext = undefined;
|
|
1241
|
+
this.protocolV2RuntimeContextPromise = undefined;
|
|
1242
|
+
this.protocolV2RuntimeContextRequestToken = undefined;
|
|
1145
1243
|
this.clearPreInitialized();
|
|
1146
1244
|
let loaderMode: 'bootloader' | 'romloader' | undefined;
|
|
1147
1245
|
if (rebootType === DeviceRebootType.Bootloader) {
|
|
@@ -1494,6 +1592,75 @@ export class Device extends EventEmitter {
|
|
|
1494
1592
|
return res.message;
|
|
1495
1593
|
}
|
|
1496
1594
|
|
|
1595
|
+
beginProtocolV2UiInteraction() {
|
|
1596
|
+
if (!this.isProtocolV2()) return;
|
|
1597
|
+
this.protocolV2UiInteraction = {
|
|
1598
|
+
interactionId: `${this.instanceId}:${Date.now()}:${++this.protocolV2UiInteractionCounter}`,
|
|
1599
|
+
phaseCounter: 0,
|
|
1600
|
+
sequence: 0,
|
|
1601
|
+
opened: false,
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
createProtocolV2UiPhaseMetadata(
|
|
1606
|
+
phase: HardwareUiInteractionMeta['phase'],
|
|
1607
|
+
transition: HardwareUiInteractionMeta['transition'],
|
|
1608
|
+
options?: {
|
|
1609
|
+
phaseId?: string;
|
|
1610
|
+
outcome?: HardwareUiInteractionMeta['outcome'];
|
|
1611
|
+
}
|
|
1612
|
+
): HardwareUiInteractionMeta | undefined {
|
|
1613
|
+
if (!this.isProtocolV2()) return undefined;
|
|
1614
|
+
if (!this.protocolV2UiInteraction) this.beginProtocolV2UiInteraction();
|
|
1615
|
+
|
|
1616
|
+
const interaction = this.protocolV2UiInteraction;
|
|
1617
|
+
if (!interaction) return undefined;
|
|
1618
|
+
const phaseId =
|
|
1619
|
+
options?.phaseId ?? `${interaction.interactionId}:phase-${++interaction.phaseCounter}`;
|
|
1620
|
+
interaction.opened = true;
|
|
1621
|
+
interaction.sequence += 1;
|
|
1622
|
+
|
|
1623
|
+
return {
|
|
1624
|
+
interactionId: interaction.interactionId,
|
|
1625
|
+
phaseId,
|
|
1626
|
+
sequence: interaction.sequence,
|
|
1627
|
+
phase,
|
|
1628
|
+
transition,
|
|
1629
|
+
...(options?.outcome ? { outcome: options.outcome } : {}),
|
|
1630
|
+
protocol: 'V2',
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
completeProtocolV2UiPhase(
|
|
1635
|
+
phase: HardwareUiInteractionMeta,
|
|
1636
|
+
outcome: HardwareUiInteractionMeta['outcome'] = 'succeeded'
|
|
1637
|
+
) {
|
|
1638
|
+
return this.createProtocolV2UiPhaseMetadata(phase.phase, 'complete', {
|
|
1639
|
+
phaseId: phase.phaseId,
|
|
1640
|
+
outcome,
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
finishProtocolV2UiInteraction(outcome: HardwareUiInteractionMeta['outcome'] = 'succeeded') {
|
|
1645
|
+
const interaction = this.protocolV2UiInteraction;
|
|
1646
|
+
if (!interaction?.opened) {
|
|
1647
|
+
this.protocolV2UiInteraction = undefined;
|
|
1648
|
+
return undefined;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
const phaseId = `${interaction.interactionId}:phase-${Math.max(interaction.phaseCounter, 1)}`;
|
|
1652
|
+
const metadata = this.createProtocolV2UiPhaseMetadata('processing', 'finish', {
|
|
1653
|
+
phaseId,
|
|
1654
|
+
outcome,
|
|
1655
|
+
});
|
|
1656
|
+
this.protocolV2UiInteraction = undefined;
|
|
1657
|
+
return metadata;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
hasOpenProtocolV2UiInteraction() {
|
|
1661
|
+
return this.protocolV2UiInteraction?.opened === true;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1497
1664
|
supportUnlockVersionRange(): DeviceFirmwareRange {
|
|
1498
1665
|
// This range applies to Protocol V1 Pro devices; Pro2 has a dedicated unlock flow.
|
|
1499
1666
|
return {
|
|
@@ -1503,10 +1670,33 @@ export class Device extends EventEmitter {
|
|
|
1503
1670
|
};
|
|
1504
1671
|
}
|
|
1505
1672
|
|
|
1506
|
-
async unlockDevice(
|
|
1673
|
+
async unlockDevice(
|
|
1674
|
+
pinType?: DeviceSessionPinType,
|
|
1675
|
+
options?: ProtocolV2UiEventMetadata & { emitUiEvent?: boolean }
|
|
1676
|
+
) {
|
|
1507
1677
|
if (this.isProtocolV2()) {
|
|
1678
|
+
const requestedPinType = pinType ?? DeviceSessionPinType.Main;
|
|
1679
|
+
const interaction =
|
|
1680
|
+
options?.interaction ??
|
|
1681
|
+
(options?.emitUiEvent === false
|
|
1682
|
+
? undefined
|
|
1683
|
+
: this.createProtocolV2UiPhaseMetadata('pin', 'start'));
|
|
1684
|
+
if (options?.emitUiEvent !== false) {
|
|
1685
|
+
this.emit(DEVICE.PIN_ON_DEVICE, this, requestedPinType, {
|
|
1686
|
+
source: options?.source ?? 'unlock-coordinator',
|
|
1687
|
+
reason: options?.reason ?? 'device-unlock',
|
|
1688
|
+
deviceOnly: options?.deviceOnly ?? true,
|
|
1689
|
+
completion: options?.completion,
|
|
1690
|
+
method: options?.method,
|
|
1691
|
+
page: options?.page,
|
|
1692
|
+
operation: options?.operation,
|
|
1693
|
+
interaction: options?.interaction ?? interaction,
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1508
1696
|
try {
|
|
1509
|
-
await this.commands.typedCall('DeviceSessionAskPin', 'Success', {
|
|
1697
|
+
await this.commands.typedCall('DeviceSessionAskPin', 'Success', {
|
|
1698
|
+
type: requestedPinType,
|
|
1699
|
+
});
|
|
1510
1700
|
} catch (error) {
|
|
1511
1701
|
const errorText =
|
|
1512
1702
|
error instanceof Error
|
|
@@ -1518,6 +1708,11 @@ export class Device extends EventEmitter {
|
|
|
1518
1708
|
throw error;
|
|
1519
1709
|
}
|
|
1520
1710
|
|
|
1711
|
+
const completion = interaction ? this.completeProtocolV2UiPhase(interaction) : undefined;
|
|
1712
|
+
if (completion) {
|
|
1713
|
+
this.emit(DEVICE.PIN_ON_DEVICE_COMPLETE, this, completion);
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1521
1716
|
const status = await requestProtocolV2DeviceStatus({ commands: this.commands });
|
|
1522
1717
|
return this.updateProtocolV2Status(status);
|
|
1523
1718
|
}
|
|
@@ -1579,7 +1774,8 @@ export class Device extends EventEmitter {
|
|
|
1579
1774
|
async checkPassphraseStateSafety(
|
|
1580
1775
|
passphraseState?: string,
|
|
1581
1776
|
useEmptyPassphrase?: boolean,
|
|
1582
|
-
skipPassphraseCheck?: boolean
|
|
1777
|
+
skipPassphraseCheck?: boolean,
|
|
1778
|
+
deriveCardano?: boolean
|
|
1583
1779
|
) {
|
|
1584
1780
|
if (this.isUnacquired()) return false;
|
|
1585
1781
|
|
|
@@ -1588,6 +1784,7 @@ export class Device extends EventEmitter {
|
|
|
1588
1784
|
await getPassphraseStateWithRefreshDeviceInfo(this, {
|
|
1589
1785
|
expectPassphraseState: expectedPassphraseState,
|
|
1590
1786
|
onlyMainPin: useEmptyPassphrase,
|
|
1787
|
+
deriveCardano,
|
|
1591
1788
|
});
|
|
1592
1789
|
|
|
1593
1790
|
// Main wallet and unlock Attach Pin, throw safe error
|
package/src/events/device.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MessageFactoryFn } from './utils';
|
|
2
|
+
import type { HardwareUiInteractionMeta } from './ui-request';
|
|
2
3
|
import type { PROTO } from '../constants';
|
|
3
4
|
import type {
|
|
4
5
|
KnownDevice as Device,
|
|
@@ -26,6 +27,8 @@ export const DEVICE = {
|
|
|
26
27
|
// onekey-transport events in protobuf format
|
|
27
28
|
BUTTON: 'button',
|
|
28
29
|
PIN: 'pin',
|
|
30
|
+
PIN_ON_DEVICE: 'pin_on_device',
|
|
31
|
+
PIN_ON_DEVICE_COMPLETE: 'pin_on_device_complete',
|
|
29
32
|
PASSPHRASE: 'passphrase',
|
|
30
33
|
PASSPHRASE_ON_DEVICE: 'passphrase_on_device',
|
|
31
34
|
ATTACH_PIN_ON_DEVICE: 'attach_pin_on_device',
|
|
@@ -58,6 +61,7 @@ export type PassphraseRequestPayload = {
|
|
|
58
61
|
source?: 'wallet-session-coordinator';
|
|
59
62
|
reason?: 'open-wallet' | 'session-recovery';
|
|
60
63
|
expectedPassphraseState?: string;
|
|
64
|
+
interaction?: HardwareUiInteractionMeta;
|
|
61
65
|
};
|
|
62
66
|
|
|
63
67
|
export interface DeviceButtonRequest {
|