@onekeyfe/hd-core 1.2.2-alpha.12 → 1.2.2-alpha.120
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/README.md +1 -1
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +452 -9
- package/__tests__/DeviceCommands.test.ts +254 -1
- package/__tests__/core-error-output.test.ts +92 -1
- package/__tests__/device-lifecycle-events.test.ts +379 -15
- package/__tests__/open-wallet-session-error-response.test.ts +2 -2
- package/__tests__/open-wallet-session.test.ts +8 -411
- package/__tests__/protocol-v2.test.ts +86 -0
- package/__tests__/public-device-state-api.test.ts +2 -7
- package/__tests__/sol-sign-offchain-message.test.ts +64 -0
- package/dist/api/GetFeatures.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/UploadPortfolio.d.ts +1 -0
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddress.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts +6 -0
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressByLoop.d.ts.map +1 -1
- package/dist/api/device/DeviceVerify.d.ts.map +1 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts.map +1 -1
- package/dist/core/RequestQueue.d.ts +1 -0
- package/dist/core/RequestQueue.d.ts.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/uiPromiseRegistry.d.ts +1 -1
- package/dist/device/Device.d.ts +2 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +5 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/index.d.ts +35 -30
- package/dist/index.js +402 -208
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/getFeatures.d.ts.map +1 -1
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +1 -10
- package/dist/types/api/openWalletSession.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -4
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/api/solSignOffchainMessage.d.ts +1 -0
- package/dist/types/api/solSignOffchainMessage.d.ts.map +1 -1
- package/dist/types/params.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/GetFeatures.ts +1 -0
- package/src/api/GetPassphraseState.ts +1 -0
- package/src/api/OpenWalletSession.ts +7 -77
- package/src/api/UploadPortfolio.ts +5 -4
- package/src/api/allnetwork/AllNetworkGetAddress.ts +47 -20
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +88 -22
- package/src/api/allnetwork/AllNetworkGetAddressByLoop.ts +3 -0
- package/src/api/device/DeviceVerify.ts +8 -0
- package/src/api/solana/SolSignOffchainMessage.ts +44 -4
- package/src/core/RequestQueue.ts +20 -0
- package/src/core/index.ts +116 -49
- package/src/data/messages/messages-protocol-v2.json +49 -33
- package/src/data/messages/messages.json +8 -5
- package/src/device/Device.ts +51 -27
- package/src/device/DeviceCommands.ts +29 -2
- package/src/protocols/protocol-v2/walletSession.ts +7 -0
- package/src/types/api/getFeatures.ts +2 -1
- package/src/types/api/getPassphraseState.ts +2 -5
- package/src/types/api/openWalletSession.ts +7 -19
- package/src/types/api/protocolV2.ts +3 -4
- package/src/types/api/solSignOffchainMessage.ts +2 -0
- package/src/types/params.ts +7 -0
package/src/core/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
isProtocolV2LinkDisabledError,
|
|
11
11
|
} from '@onekeyfe/hd-transport';
|
|
12
12
|
import {
|
|
13
|
+
EDeviceType,
|
|
13
14
|
ERRORS,
|
|
14
15
|
ERROR_CODES_REQUIRE_DISCONNECT,
|
|
15
16
|
ERROR_CODES_REQUIRE_RELEASE,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
createNeedUpgradeFirmwareHardwareError,
|
|
23
24
|
createNewFirmwareForceUpdateHardwareError,
|
|
24
25
|
createNewFirmwareUnReleaseHardwareError,
|
|
26
|
+
isBleStaleBondHardwareError,
|
|
25
27
|
} from '@onekeyfe/hd-shared';
|
|
26
28
|
|
|
27
29
|
import { LoggerNames, enableLog, getLogger, setLoggerPostMessage, wait } from '../utils';
|
|
@@ -83,6 +85,7 @@ import type { BaseMethod } from '../api/BaseMethod';
|
|
|
83
85
|
const Log = getLogger(LoggerNames.Core);
|
|
84
86
|
const PRE_INITIALIZE_TTL_MS = 60 * 1000;
|
|
85
87
|
const PRE_PENDING_CALL_TIMEOUT_MS = 15 * 1000;
|
|
88
|
+
const PRO2_USB_SIGNING_COOLDOWN_MS = 1000;
|
|
86
89
|
|
|
87
90
|
// Dedup/coalesce state for "pre-warm signal" methods (isPreWarmSignal),
|
|
88
91
|
// keyed by getPreWarmKey(): coalesce in-flight, skip if warmed within TTL.
|
|
@@ -312,30 +315,44 @@ const waitForPendingPromise = async (
|
|
|
312
315
|
if (pendingPromise) {
|
|
313
316
|
Log.debug('pre pending call promise before call method, wait for it');
|
|
314
317
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
315
|
-
let timedOut = false;
|
|
316
318
|
try {
|
|
317
319
|
await Promise.race([
|
|
318
320
|
pendingPromise,
|
|
319
|
-
new Promise<void>(
|
|
321
|
+
new Promise<void>((_, reject) => {
|
|
320
322
|
timer = setTimeout(() => {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
+
reject(
|
|
324
|
+
ERRORS.TypedError(
|
|
325
|
+
HardwareErrorCode.DeviceBusy,
|
|
326
|
+
'Previous device cancellation is still draining'
|
|
327
|
+
)
|
|
328
|
+
);
|
|
323
329
|
}, PRE_PENDING_CALL_TIMEOUT_MS);
|
|
324
330
|
}),
|
|
325
331
|
]);
|
|
326
|
-
|
|
327
|
-
//
|
|
332
|
+
// A deadline is not evidence that old I/O is safe to reuse. Keep the
|
|
333
|
+
// barrier on failure; a later call may proceed only after cleanup settles.
|
|
334
|
+
removePrePendingCallPromise?.(connectId, pendingPromise);
|
|
328
335
|
} finally {
|
|
329
336
|
if (timer) clearTimeout(timer);
|
|
330
|
-
removePrePendingCallPromise?.(connectId, pendingPromise);
|
|
331
|
-
}
|
|
332
|
-
if (timedOut) {
|
|
333
|
-
Log.warn('pre pending call promise timed out before call method', { connectId });
|
|
334
337
|
}
|
|
335
338
|
Log.debug('pre pending call promise before call method done');
|
|
336
339
|
}
|
|
337
340
|
};
|
|
338
341
|
|
|
342
|
+
export function getPostCallPendingPromise(method: BaseMethod, device: Device): Promise<void> {
|
|
343
|
+
const cleanupPromise = device.waitForRunCleanup();
|
|
344
|
+
const env = DataManager.getSettings('env');
|
|
345
|
+
const deviceType = device.getCurrentDeviceType();
|
|
346
|
+
const requiresSigningCooldown =
|
|
347
|
+
method.name.includes('Sign') &&
|
|
348
|
+
(deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo) &&
|
|
349
|
+
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'node-usb');
|
|
350
|
+
|
|
351
|
+
return requiresSigningCooldown
|
|
352
|
+
? cleanupPromise.then(() => wait(PRO2_USB_SIGNING_COOLDOWN_MS)).then(() => undefined)
|
|
353
|
+
: cleanupPromise;
|
|
354
|
+
}
|
|
355
|
+
|
|
339
356
|
const onCallDevice = async (
|
|
340
357
|
context: CoreContext,
|
|
341
358
|
message: CoreMessage,
|
|
@@ -366,42 +383,38 @@ const onCallDevice = async (
|
|
|
366
383
|
DevicePool.clearDeviceCache(method.payload.connectId);
|
|
367
384
|
}
|
|
368
385
|
|
|
369
|
-
//
|
|
370
|
-
if (method.connectId) {
|
|
371
|
-
await context.waitForCallbackTasks(method.connectId);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
await waitForPendingPromise(
|
|
375
|
-
method.connectId ?? '',
|
|
376
|
-
getPrePendingCallPromise,
|
|
377
|
-
removePrePendingCallPromise
|
|
378
|
-
);
|
|
379
|
-
|
|
386
|
+
// Register before waiting so cancellation also covers queued requests.
|
|
380
387
|
const task = requestQueue.createTask(method);
|
|
381
388
|
|
|
382
389
|
// Pre-warm holds the device as a per-connectId callback task so a concurrent
|
|
383
390
|
// real call waits (before ensureConnected) instead of racing its Initialize.
|
|
384
391
|
// Only covers pre-warm -> real-call ordering; the reverse is fail-closed.
|
|
385
392
|
let preWarmCallbackTask: Deferred<void> | undefined;
|
|
386
|
-
if (method.isPreWarmSignal && method.connectId) {
|
|
387
|
-
preWarmCallbackTask = createDeferred<void>();
|
|
388
|
-
context.registerCallbackTask(method.connectId, preWarmCallbackTask);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
393
|
let device: Device;
|
|
392
394
|
try {
|
|
395
|
+
const connectId = method.connectId ?? '';
|
|
396
|
+
if (connectId) {
|
|
397
|
+
await requestQueue.waitForTask(task, () => context.waitForCallbackTasks(connectId));
|
|
398
|
+
}
|
|
399
|
+
await requestQueue.waitForTask(task, () =>
|
|
400
|
+
waitForPendingPromise(
|
|
401
|
+
method.connectId ?? '',
|
|
402
|
+
getPrePendingCallPromise,
|
|
403
|
+
removePrePendingCallPromise
|
|
404
|
+
)
|
|
405
|
+
);
|
|
406
|
+
if (method.isPreWarmSignal && method.connectId) {
|
|
407
|
+
preWarmCallbackTask = createDeferred<void>();
|
|
408
|
+
context.registerCallbackTask(method.connectId, preWarmCallbackTask);
|
|
409
|
+
}
|
|
393
410
|
/**
|
|
394
411
|
* Polling to ensure successful connection
|
|
395
412
|
*/
|
|
396
|
-
const connectId = method.connectId ?? '';
|
|
397
413
|
const pollingId = pollingManager.start(connectId);
|
|
398
|
-
device = await ensureConnected(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
pollingId,
|
|
403
|
-
task.abortController?.signal
|
|
404
|
-
);
|
|
414
|
+
device = await ensureConnected(context, method, connectId, pollingId, method.abortSignal);
|
|
415
|
+
if (method.abortSignal?.aborted) {
|
|
416
|
+
throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
417
|
+
}
|
|
405
418
|
} catch (e) {
|
|
406
419
|
preWarmCallbackTask?.resolve();
|
|
407
420
|
Log.debug('ensureConnected error: ', e);
|
|
@@ -477,14 +490,19 @@ const onCallDevice = async (
|
|
|
477
490
|
|
|
478
491
|
try {
|
|
479
492
|
// Wait for any pending task except our own (self-wait would deadlock).
|
|
480
|
-
|
|
481
|
-
|
|
493
|
+
const { connectId } = method;
|
|
494
|
+
if (connectId) {
|
|
495
|
+
await requestQueue.waitForTask(task, () =>
|
|
496
|
+
context.waitForCallbackTasks(connectId, preWarmCallbackTask)
|
|
497
|
+
);
|
|
482
498
|
}
|
|
483
499
|
|
|
484
|
-
await
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
500
|
+
await requestQueue.waitForTask(task, () =>
|
|
501
|
+
waitForPendingPromise(
|
|
502
|
+
method.connectId ?? '',
|
|
503
|
+
getPrePendingCallPromise,
|
|
504
|
+
removePrePendingCallPromise
|
|
505
|
+
)
|
|
488
506
|
);
|
|
489
507
|
|
|
490
508
|
const inner = async (): Promise<void> => {
|
|
@@ -689,6 +707,13 @@ const onCallDevice = async (
|
|
|
689
707
|
},
|
|
690
708
|
});
|
|
691
709
|
messageResponse = createResponseMessage(method.responseID, true, response);
|
|
710
|
+
// Preserve the acknowledged result while the next call waits for cleanup and cooldown.
|
|
711
|
+
if (method.connectId) {
|
|
712
|
+
context.setPrePendingCallPromise(
|
|
713
|
+
method.connectId,
|
|
714
|
+
getPostCallPendingPromise(method, device)
|
|
715
|
+
);
|
|
716
|
+
}
|
|
692
717
|
requestQueue.resolveRequest(method.responseID, messageResponse);
|
|
693
718
|
completeMethodRequestContext(method);
|
|
694
719
|
} catch (error) {
|
|
@@ -700,6 +725,12 @@ const onCallDevice = async (
|
|
|
700
725
|
}
|
|
701
726
|
Log.debug(`Call API - Inner Method Run Error`, error);
|
|
702
727
|
messageResponse = createResponseMessage(method.responseID, false, { error });
|
|
728
|
+
if (method.connectId) {
|
|
729
|
+
context.setPrePendingCallPromise(
|
|
730
|
+
method.connectId,
|
|
731
|
+
getPostCallPendingPromise(method, device)
|
|
732
|
+
);
|
|
733
|
+
}
|
|
703
734
|
requestQueue.resolveRequest(method.responseID, messageResponse);
|
|
704
735
|
completeMethodRequestContext(method, error);
|
|
705
736
|
|
|
@@ -726,6 +757,9 @@ const onCallDevice = async (
|
|
|
726
757
|
skipInitialize: canSkipInitialize(method, device),
|
|
727
758
|
...parseInitOptions(method),
|
|
728
759
|
};
|
|
760
|
+
if (method.abortSignal?.aborted) {
|
|
761
|
+
throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
762
|
+
}
|
|
729
763
|
const deviceRun = () => device.run(inner, runOptions);
|
|
730
764
|
task.callPromise = createDeferred<any>(deviceRun);
|
|
731
765
|
|
|
@@ -746,6 +780,7 @@ const onCallDevice = async (
|
|
|
746
780
|
);
|
|
747
781
|
Log.debug('Call API - Run Error: ', error);
|
|
748
782
|
completeMethodRequestContext(method, error);
|
|
783
|
+
return messageResponse;
|
|
749
784
|
} finally {
|
|
750
785
|
// Release the pre-warm callback task so the next real call can proceed.
|
|
751
786
|
preWarmCallbackTask?.resolve();
|
|
@@ -945,7 +980,16 @@ export function isRetryableBleConnectionError(method: BaseMethod, error: unknown
|
|
|
945
980
|
if (method.device?.wasInterruptedByUser()) {
|
|
946
981
|
return false;
|
|
947
982
|
}
|
|
948
|
-
const typedError = error as {
|
|
983
|
+
const typedError = error as {
|
|
984
|
+
errorCode?: unknown;
|
|
985
|
+
params?: { acquireDeadlineExceeded?: unknown };
|
|
986
|
+
};
|
|
987
|
+
if (
|
|
988
|
+
typedError?.errorCode === HardwareErrorCode.BleTimeoutError &&
|
|
989
|
+
typedError.params?.acquireDeadlineExceeded === true
|
|
990
|
+
) {
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
949
993
|
return (
|
|
950
994
|
typedError?.errorCode === HardwareErrorCode.BleTimeoutError ||
|
|
951
995
|
typedError?.errorCode === HardwareErrorCode.BleConnectedError ||
|
|
@@ -964,11 +1008,13 @@ export function isMissingDetectedProtocolV2Error(method: BaseMethod, error: unkn
|
|
|
964
1008
|
);
|
|
965
1009
|
}
|
|
966
1010
|
|
|
967
|
-
export function
|
|
1011
|
+
export function isTerminalBleStaleBondError(error: unknown) {
|
|
1012
|
+
return isBleStaleBondHardwareError(error);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
export function isDeviceIdentityMismatchError(error: unknown) {
|
|
968
1016
|
return (
|
|
969
|
-
|
|
970
|
-
(error as { errorCode?: unknown })?.errorCode ===
|
|
971
|
-
HardwareErrorCode.BlePeerRemovedPairingInformation
|
|
1017
|
+
(error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.DeviceCheckDeviceIdError
|
|
972
1018
|
);
|
|
973
1019
|
}
|
|
974
1020
|
|
|
@@ -1001,7 +1047,8 @@ function raceBleAcquire<T>(acquirePromise: Promise<T>, abortSignal?: AbortSignal
|
|
|
1001
1047
|
reject(
|
|
1002
1048
|
ERRORS.TypedError(
|
|
1003
1049
|
HardwareErrorCode.BleTimeoutError,
|
|
1004
|
-
`BLE acquire exceeded ${BLE_ACQUIRE_DEADLINE_MS}ms deadline
|
|
1050
|
+
`BLE acquire exceeded ${BLE_ACQUIRE_DEADLINE_MS}ms deadline`,
|
|
1051
|
+
{ acquireDeadlineExceeded: true }
|
|
1005
1052
|
)
|
|
1006
1053
|
)
|
|
1007
1054
|
),
|
|
@@ -1031,7 +1078,7 @@ async function connectDeviceForBle(
|
|
|
1031
1078
|
retryCount = 0
|
|
1032
1079
|
) {
|
|
1033
1080
|
try {
|
|
1034
|
-
if (device.wasInterruptedByUser()) {
|
|
1081
|
+
if (abortSignal?.aborted || device.wasInterruptedByUser()) {
|
|
1035
1082
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
1036
1083
|
}
|
|
1037
1084
|
if (method.payload.forceProtocolDetection && device.hasDeviceAcquire()) {
|
|
@@ -1100,6 +1147,9 @@ async function connectDeviceForBle(
|
|
|
1100
1147
|
DevicePool.emitter.emit(DEVICE.CONNECT, device);
|
|
1101
1148
|
}
|
|
1102
1149
|
} catch (err) {
|
|
1150
|
+
if (abortSignal?.aborted || device.wasInterruptedByUser()) {
|
|
1151
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
1152
|
+
}
|
|
1103
1153
|
const requiresColdReconnect = isMissingDetectedProtocolV2Error(method, err);
|
|
1104
1154
|
// Device.run()'s REQUIRE_DISCONNECT handling never sees acquire/initialize
|
|
1105
1155
|
// failures, so with keep-alive a wedged link would be reused by every retry
|
|
@@ -1273,6 +1323,8 @@ const ensureConnected = async (
|
|
|
1273
1323
|
HardwareErrorCode.BleDeviceBondedCanceled,
|
|
1274
1324
|
HardwareErrorCode.BleCharacteristicNotifyError,
|
|
1275
1325
|
HardwareErrorCode.BleTimeoutError,
|
|
1326
|
+
// A transport setup reset must also stop this outer poll.
|
|
1327
|
+
HardwareErrorCode.PollingTimeout,
|
|
1276
1328
|
HardwareErrorCode.BleWriteCharacteristicError,
|
|
1277
1329
|
HardwareErrorCode.BleAlreadyConnected,
|
|
1278
1330
|
HardwareErrorCode.FirmwareUpdateLimitOneDevice,
|
|
@@ -1283,7 +1335,12 @@ const ensureConnected = async (
|
|
|
1283
1335
|
HardwareErrorCode.DeviceInterruptedFromUser,
|
|
1284
1336
|
HardwareErrorCode.CallQueueActionCancelled,
|
|
1285
1337
|
].includes(error.errorCode) ||
|
|
1286
|
-
|
|
1338
|
+
(env === 'react-native' &&
|
|
1339
|
+
[HardwareErrorCode.BleDeviceDisconnected, HardwareErrorCode.PollingTimeout].includes(
|
|
1340
|
+
error.errorCode
|
|
1341
|
+
)) ||
|
|
1342
|
+
isTerminalBleStaleBondError(error) ||
|
|
1343
|
+
isDeviceIdentityMismatchError(error)
|
|
1287
1344
|
) {
|
|
1288
1345
|
reject(error);
|
|
1289
1346
|
return;
|
|
@@ -1760,7 +1817,17 @@ export default class Core extends EventEmitter {
|
|
|
1760
1817
|
this.prePendingCallPromises.delete(connectId);
|
|
1761
1818
|
return;
|
|
1762
1819
|
}
|
|
1763
|
-
this.prePendingCallPromises.
|
|
1820
|
+
const previous = this.prePendingCallPromises.get(connectId);
|
|
1821
|
+
const cleanupPromise =
|
|
1822
|
+
previous && previous !== promise
|
|
1823
|
+
? Promise.all([previous, promise]).then(() => undefined)
|
|
1824
|
+
: promise;
|
|
1825
|
+
this.prePendingCallPromises.set(connectId, cleanupPromise);
|
|
1826
|
+
// cancel() is fire-and-forget. Observe failures immediately, while
|
|
1827
|
+
// preserving the rejected barrier for the next caller's safety check.
|
|
1828
|
+
cleanupPromise.catch(error => {
|
|
1829
|
+
Log.warn('Device cancellation cleanup failed', { errorCode: error?.errorCode });
|
|
1830
|
+
});
|
|
1764
1831
|
},
|
|
1765
1832
|
removePrePendingCallPromise: (connectId: string, promise: Promise<void>) => {
|
|
1766
1833
|
if (this.prePendingCallPromises.get(connectId) === promise) {
|
|
@@ -4463,10 +4463,6 @@
|
|
|
4463
4463
|
"request_id": {
|
|
4464
4464
|
"type": "bytes",
|
|
4465
4465
|
"id": 5
|
|
4466
|
-
},
|
|
4467
|
-
"source_fingerprint": {
|
|
4468
|
-
"type": "uint32",
|
|
4469
|
-
"id": 6
|
|
4470
4466
|
}
|
|
4471
4467
|
}
|
|
4472
4468
|
},
|
|
@@ -4722,10 +4718,6 @@
|
|
|
4722
4718
|
"expected_address": {
|
|
4723
4719
|
"type": "bytes",
|
|
4724
4720
|
"id": 12
|
|
4725
|
-
},
|
|
4726
|
-
"source_fingerprint": {
|
|
4727
|
-
"type": "uint32",
|
|
4728
|
-
"id": 13
|
|
4729
4721
|
}
|
|
4730
4722
|
}
|
|
4731
4723
|
},
|
|
@@ -4810,10 +4802,6 @@
|
|
|
4810
4802
|
"expected_address": {
|
|
4811
4803
|
"type": "bytes",
|
|
4812
4804
|
"id": 12
|
|
4813
|
-
},
|
|
4814
|
-
"source_fingerprint": {
|
|
4815
|
-
"type": "uint32",
|
|
4816
|
-
"id": 13
|
|
4817
4805
|
}
|
|
4818
4806
|
}
|
|
4819
4807
|
},
|
|
@@ -4961,7 +4949,14 @@
|
|
|
4961
4949
|
"type": "EthereumAuthorizationSignature",
|
|
4962
4950
|
"id": 10
|
|
4963
4951
|
}
|
|
4964
|
-
}
|
|
4952
|
+
},
|
|
4953
|
+
"reserved": [
|
|
4954
|
+
[5, 5],
|
|
4955
|
+
[6, 6],
|
|
4956
|
+
[7, 7],
|
|
4957
|
+
[8, 8],
|
|
4958
|
+
[9, 9]
|
|
4959
|
+
]
|
|
4965
4960
|
},
|
|
4966
4961
|
"EthereumTxAckOneKey": {
|
|
4967
4962
|
"fields": {
|
|
@@ -4990,10 +4985,6 @@
|
|
|
4990
4985
|
"chain_id": {
|
|
4991
4986
|
"type": "uint64",
|
|
4992
4987
|
"id": 3
|
|
4993
|
-
},
|
|
4994
|
-
"source_fingerprint": {
|
|
4995
|
-
"type": "uint32",
|
|
4996
|
-
"id": 4
|
|
4997
4988
|
}
|
|
4998
4989
|
}
|
|
4999
4990
|
},
|
|
@@ -8968,7 +8959,8 @@
|
|
|
8968
8959
|
},
|
|
8969
8960
|
"SolanaOffChainMessageVersion": {
|
|
8970
8961
|
"values": {
|
|
8971
|
-
"MESSAGE_VERSION_0": 0
|
|
8962
|
+
"MESSAGE_VERSION_0": 0,
|
|
8963
|
+
"MESSAGE_VERSION_1": 1
|
|
8972
8964
|
}
|
|
8973
8965
|
},
|
|
8974
8966
|
"SolanaOffChainMessageFormat": {
|
|
@@ -9016,10 +9008,6 @@
|
|
|
9016
9008
|
"rule": "required",
|
|
9017
9009
|
"type": "bytes",
|
|
9018
9010
|
"id": 2
|
|
9019
|
-
},
|
|
9020
|
-
"source_fingerprint": {
|
|
9021
|
-
"type": "uint32",
|
|
9022
|
-
"id": 3
|
|
9023
9011
|
}
|
|
9024
9012
|
}
|
|
9025
9013
|
},
|
|
@@ -9056,17 +9044,15 @@
|
|
|
9056
9044
|
},
|
|
9057
9045
|
"message_format": {
|
|
9058
9046
|
"type": "SolanaOffChainMessageFormat",
|
|
9059
|
-
"id": 4
|
|
9060
|
-
"options": {
|
|
9061
|
-
"default": "V0_RESTRICTED_ASCII"
|
|
9062
|
-
}
|
|
9047
|
+
"id": 4
|
|
9063
9048
|
},
|
|
9064
9049
|
"application_domain": {
|
|
9065
9050
|
"type": "bytes",
|
|
9066
9051
|
"id": 5
|
|
9067
9052
|
},
|
|
9068
|
-
"
|
|
9069
|
-
"
|
|
9053
|
+
"required_signers": {
|
|
9054
|
+
"rule": "repeated",
|
|
9055
|
+
"type": "bytes",
|
|
9070
9056
|
"id": 6
|
|
9071
9057
|
}
|
|
9072
9058
|
}
|
|
@@ -9085,10 +9071,6 @@
|
|
|
9085
9071
|
"rule": "required",
|
|
9086
9072
|
"type": "bytes",
|
|
9087
9073
|
"id": 2
|
|
9088
|
-
},
|
|
9089
|
-
"source_fingerprint": {
|
|
9090
|
-
"type": "uint32",
|
|
9091
|
-
"id": 3
|
|
9092
9074
|
}
|
|
9093
9075
|
}
|
|
9094
9076
|
},
|
|
@@ -11501,6 +11483,7 @@
|
|
|
11501
11483
|
"MessageType_DeviceFindMyTokenState": 60450,
|
|
11502
11484
|
"MessageType_DeviceFindMyTokenUpdate": 60451,
|
|
11503
11485
|
"MessageType_DeviceFindMyTokenStateGet": 60452,
|
|
11486
|
+
"MessageType_DeviceAnimationControl": 60461,
|
|
11504
11487
|
"MessageType_DeviceInfoGet": 60600,
|
|
11505
11488
|
"MessageType_DeviceInfo": 60601,
|
|
11506
11489
|
"MessageType_DeviceStatusGet": 60602,
|
|
@@ -11824,6 +11807,26 @@
|
|
|
11824
11807
|
}
|
|
11825
11808
|
}
|
|
11826
11809
|
},
|
|
11810
|
+
"DeviceAnimationAction": {
|
|
11811
|
+
"values": {
|
|
11812
|
+
"AnimationAction_Unknown": 0,
|
|
11813
|
+
"AnimationAction_Start": 1,
|
|
11814
|
+
"AnimationAction_Stop": 2
|
|
11815
|
+
}
|
|
11816
|
+
},
|
|
11817
|
+
"DeviceAnimationControl": {
|
|
11818
|
+
"fields": {
|
|
11819
|
+
"action": {
|
|
11820
|
+
"rule": "required",
|
|
11821
|
+
"type": "DeviceAnimationAction",
|
|
11822
|
+
"id": 1
|
|
11823
|
+
},
|
|
11824
|
+
"timeout_ms": {
|
|
11825
|
+
"type": "uint32",
|
|
11826
|
+
"id": 2
|
|
11827
|
+
}
|
|
11828
|
+
}
|
|
11829
|
+
},
|
|
11827
11830
|
"DeviceFactoryAck": {
|
|
11828
11831
|
"values": {
|
|
11829
11832
|
"FACTORY_ACK_SUCCESS": 0,
|
|
@@ -11885,6 +11888,10 @@
|
|
|
11885
11888
|
"manufacture_time": {
|
|
11886
11889
|
"type": "DeviceFactoryInfoManufactureTime",
|
|
11887
11890
|
"id": 5
|
|
11891
|
+
},
|
|
11892
|
+
"data": {
|
|
11893
|
+
"type": "bytes",
|
|
11894
|
+
"id": 100
|
|
11888
11895
|
}
|
|
11889
11896
|
}
|
|
11890
11897
|
},
|
|
@@ -11894,11 +11901,20 @@
|
|
|
11894
11901
|
"rule": "required",
|
|
11895
11902
|
"type": "DeviceFactoryInfo",
|
|
11896
11903
|
"id": 1
|
|
11904
|
+
},
|
|
11905
|
+
"full_data": {
|
|
11906
|
+
"type": "bool",
|
|
11907
|
+
"id": 2
|
|
11897
11908
|
}
|
|
11898
11909
|
}
|
|
11899
11910
|
},
|
|
11900
11911
|
"DeviceFactoryInfoGet": {
|
|
11901
|
-
"fields": {
|
|
11912
|
+
"fields": {
|
|
11913
|
+
"full_data": {
|
|
11914
|
+
"type": "bool",
|
|
11915
|
+
"id": 1
|
|
11916
|
+
}
|
|
11917
|
+
}
|
|
11902
11918
|
},
|
|
11903
11919
|
"DeviceFactoryPermanentLock": {
|
|
11904
11920
|
"fields": {
|
|
@@ -10243,7 +10243,8 @@
|
|
|
10243
10243
|
},
|
|
10244
10244
|
"SolanaOffChainMessageVersion": {
|
|
10245
10245
|
"values": {
|
|
10246
|
-
"MESSAGE_VERSION_0": 0
|
|
10246
|
+
"MESSAGE_VERSION_0": 0,
|
|
10247
|
+
"MESSAGE_VERSION_1": 1
|
|
10247
10248
|
}
|
|
10248
10249
|
},
|
|
10249
10250
|
"SolanaOffChainMessageFormat": {
|
|
@@ -10276,14 +10277,16 @@
|
|
|
10276
10277
|
},
|
|
10277
10278
|
"message_format": {
|
|
10278
10279
|
"type": "SolanaOffChainMessageFormat",
|
|
10279
|
-
"id": 4
|
|
10280
|
-
"options": {
|
|
10281
|
-
"default": "V0_RESTRICTED_ASCII"
|
|
10282
|
-
}
|
|
10280
|
+
"id": 4
|
|
10283
10281
|
},
|
|
10284
10282
|
"application_domain": {
|
|
10285
10283
|
"type": "bytes",
|
|
10286
10284
|
"id": 5
|
|
10285
|
+
},
|
|
10286
|
+
"required_signers": {
|
|
10287
|
+
"rule": "repeated",
|
|
10288
|
+
"type": "bytes",
|
|
10289
|
+
"id": 7
|
|
10287
10290
|
}
|
|
10288
10291
|
}
|
|
10289
10292
|
},
|
package/src/device/Device.ts
CHANGED
|
@@ -297,6 +297,8 @@ export class Device extends EventEmitter {
|
|
|
297
297
|
/** Resolves only after the active run has completed its release path. */
|
|
298
298
|
private runCleanupPromise?: Promise<void>;
|
|
299
299
|
|
|
300
|
+
private userInterruption?: { attempt: number; promise: Promise<void> };
|
|
301
|
+
|
|
300
302
|
externalState: string[] = [];
|
|
301
303
|
|
|
302
304
|
unavailableCapabilities: UnavailableCapabilities = {};
|
|
@@ -1462,6 +1464,10 @@ export class Device extends EventEmitter {
|
|
|
1462
1464
|
this.invalidateProtocolV2RuntimeState();
|
|
1463
1465
|
}
|
|
1464
1466
|
|
|
1467
|
+
waitForRunCleanup(): Promise<void> {
|
|
1468
|
+
return this.runCleanupPromise ?? Promise.resolve();
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1465
1471
|
async run(fn?: () => Promise<void>, options?: RunOptions) {
|
|
1466
1472
|
if (this.runPromise) {
|
|
1467
1473
|
await this.interruptionFromOutside();
|
|
@@ -1596,45 +1602,63 @@ export class Device extends EventEmitter {
|
|
|
1596
1602
|
}
|
|
1597
1603
|
}
|
|
1598
1604
|
|
|
1599
|
-
|
|
1605
|
+
interruptionFromUser(): Promise<void> {
|
|
1606
|
+
const attempt = this.connectionAttempt;
|
|
1607
|
+
if (this.userInterruption?.attempt === attempt) {
|
|
1608
|
+
return this.userInterruption.promise;
|
|
1609
|
+
}
|
|
1600
1610
|
const error = ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
1601
|
-
this.interruptedAttempt =
|
|
1611
|
+
this.interruptedAttempt = attempt;
|
|
1602
1612
|
const cleanupPromise = this.runCleanupPromise;
|
|
1603
|
-
const { cancelableAction } = this;
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1613
|
+
const { cancelableAction, commands, runPromise, deviceConnector, mainId } = this;
|
|
1614
|
+
const sendFallbackCancel = this.shouldSendFallbackProtocolCancel();
|
|
1615
|
+
const acquired = this.hasDeviceAcquire();
|
|
1616
|
+
const promise = (async () => {
|
|
1617
|
+
try {
|
|
1618
|
+
if (cancelableAction) {
|
|
1619
|
+
await cancelableAction(error);
|
|
1620
|
+
} else if (sendFallbackCancel) {
|
|
1621
|
+
await commands?.cancelDevice?.().catch(cancelError => {
|
|
1622
|
+
Log.debug('Protocol V2 fallback cancel error', cancelError);
|
|
1623
|
+
});
|
|
1624
|
+
} else if (!acquired) {
|
|
1625
|
+
// Abort setup without acquiring a session just to send Cancel.
|
|
1626
|
+
if (mainId && deviceConnector?.disconnect) {
|
|
1627
|
+
await deviceConnector.disconnect(mainId);
|
|
1628
|
+
}
|
|
1629
|
+
if (this.connectionAttempt === attempt) this.markTransportDisconnected();
|
|
1630
|
+
}
|
|
1631
|
+
await commands?.cancel();
|
|
1632
|
+
} catch (cleanupError) {
|
|
1633
|
+
Log.warn('User cancellation cleanup failed; disconnecting device', cleanupError);
|
|
1634
|
+
if (mainId && deviceConnector?.disconnect) {
|
|
1635
|
+
await deviceConnector.disconnect(mainId).catch(disconnectError => {
|
|
1636
|
+
Log.debug('User cancellation fallback disconnect failed', disconnectError);
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
if (this.connectionAttempt === attempt) this.markTransportDisconnected();
|
|
1640
|
+
} finally {
|
|
1641
|
+
runPromise?.reject(error);
|
|
1642
|
+
if (this.runPromise === runPromise) this.runPromise = null;
|
|
1643
|
+
await cleanupPromise?.catch(() => undefined);
|
|
1617
1644
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
this.runPromise.reject(error);
|
|
1624
|
-
this.runPromise = null;
|
|
1625
|
-
}
|
|
1626
|
-
await cleanupPromise?.catch(() => undefined);
|
|
1645
|
+
})();
|
|
1646
|
+
// Keep the settled promise for this attempt so a duplicate close/finally
|
|
1647
|
+
// cannot send a second wire Cancel. A new attempt gets its own cleanup.
|
|
1648
|
+
this.userInterruption = { attempt, promise };
|
|
1649
|
+
return promise;
|
|
1627
1650
|
}
|
|
1628
1651
|
|
|
1629
1652
|
setCancelableAction(callback: (err?: Error) => Promise<unknown>) {
|
|
1630
|
-
|
|
1653
|
+
const action = (e?: Error) =>
|
|
1631
1654
|
callback(e)
|
|
1632
1655
|
.catch(e2 => {
|
|
1633
1656
|
Log.debug('cancelableAction error', e2);
|
|
1634
1657
|
})
|
|
1635
1658
|
.finally(() => {
|
|
1636
|
-
this.clearCancelableAction();
|
|
1659
|
+
if (this.cancelableAction === action) this.clearCancelableAction();
|
|
1637
1660
|
});
|
|
1661
|
+
this.cancelableAction = action;
|
|
1638
1662
|
}
|
|
1639
1663
|
|
|
1640
1664
|
clearCancelableAction() {
|