@onekeyfe/hd-core 1.2.2-alpha.9 → 1.2.2
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 +503 -10
- package/__tests__/DeviceCommands.test.ts +254 -1
- package/__tests__/core-error-output.test.ts +169 -1
- package/__tests__/device-lifecycle-events.test.ts +395 -15
- package/__tests__/logBlockEvent.test.ts +45 -122
- package/__tests__/open-wallet-session-error-response.test.ts +2 -2
- package/__tests__/open-wallet-session.test.ts +8 -358
- package/__tests__/protocol-v2.test.ts +130 -0
- package/__tests__/public-device-state-api.test.ts +2 -7
- package/__tests__/search-devices.test.ts +196 -8
- package/__tests__/sol-sign-offchain-message.test.ts +64 -0
- package/dist/api/FirmwareUpdateV4.d.ts +1 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- 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/SearchDevices.d.ts +2 -15
- package/dist/api/SearchDevices.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 +2 -0
- package/dist/api/allnetwork/AllNetworkGetAddress.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts +7 -1
- 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/data-manager/TransportManager.d.ts +2 -0
- package/dist/data-manager/TransportManager.d.ts.map +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/events/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +35 -30
- package/dist/index.js +587 -314
- 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/FirmwareUpdateV4.ts +9 -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/SearchDevices.ts +121 -27
- package/src/api/UploadPortfolio.ts +5 -4
- package/src/api/allnetwork/AllNetworkGetAddress.ts +79 -45
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +95 -24
- 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 +159 -52
- package/src/data/messages/messages-protocol-v2.json +49 -33
- package/src/data/messages/messages.json +8 -5
- package/src/data-manager/TransportManager.ts +14 -3
- package/src/device/Device.ts +58 -27
- package/src/device/DeviceCommands.ts +29 -2
- package/src/events/logBlockEvent.ts +6 -75
- package/src/protocols/protocol-v2/walletSession.ts +14 -2
- 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';
|
|
@@ -82,7 +84,8 @@ import type { BaseMethod } from '../api/BaseMethod';
|
|
|
82
84
|
|
|
83
85
|
const Log = getLogger(LoggerNames.Core);
|
|
84
86
|
const PRE_INITIALIZE_TTL_MS = 60 * 1000;
|
|
85
|
-
const PRE_PENDING_CALL_TIMEOUT_MS =
|
|
87
|
+
const PRE_PENDING_CALL_TIMEOUT_MS = 5 * 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.
|
|
@@ -180,6 +183,7 @@ export const callAPI = async (context: CoreContext, message: CoreMessage) => {
|
|
|
180
183
|
}
|
|
181
184
|
};
|
|
182
185
|
method.setContext?.(context);
|
|
186
|
+
method.context = context;
|
|
183
187
|
|
|
184
188
|
method.requestContext = createRequestContext(method.responseID, method.name, {
|
|
185
189
|
sdkInstanceId: context.sdkInstanceId,
|
|
@@ -200,7 +204,12 @@ export const callAPI = async (context: CoreContext, message: CoreMessage) => {
|
|
|
200
204
|
if (!method.useDevice) {
|
|
201
205
|
updateMethodRequestContext(method, { status: 'running' });
|
|
202
206
|
try {
|
|
203
|
-
const
|
|
207
|
+
const env = DataManager.getSettings('env');
|
|
208
|
+
const response =
|
|
209
|
+
method.name === 'searchDevices' &&
|
|
210
|
+
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env))
|
|
211
|
+
? await context.methodSynchronize(() => method.run(), 'webusb-discovery')
|
|
212
|
+
: await method.run();
|
|
204
213
|
completeMethodRequestContext(method);
|
|
205
214
|
return createResponseMessage(method.responseID, true, response);
|
|
206
215
|
} catch (error) {
|
|
@@ -313,6 +322,7 @@ const waitForPendingPromise = async (
|
|
|
313
322
|
Log.debug('pre pending call promise before call method, wait for it');
|
|
314
323
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
315
324
|
let timedOut = false;
|
|
325
|
+
let completed = false;
|
|
316
326
|
try {
|
|
317
327
|
await Promise.race([
|
|
318
328
|
pendingPromise,
|
|
@@ -323,11 +333,12 @@ const waitForPendingPromise = async (
|
|
|
323
333
|
}, PRE_PENDING_CALL_TIMEOUT_MS);
|
|
324
334
|
}),
|
|
325
335
|
]);
|
|
326
|
-
|
|
327
|
-
// Cancellation is best-effort; the transport teardown owns recovery.
|
|
336
|
+
completed = !timedOut;
|
|
328
337
|
} finally {
|
|
329
338
|
if (timer) clearTimeout(timer);
|
|
330
|
-
|
|
339
|
+
// Keep a rejected cleanup barrier for the next caller's safety check;
|
|
340
|
+
// only a completed cleanup or a timeout may clear it.
|
|
341
|
+
if (timedOut || completed) removePrePendingCallPromise?.(connectId, pendingPromise);
|
|
331
342
|
}
|
|
332
343
|
if (timedOut) {
|
|
333
344
|
Log.warn('pre pending call promise timed out before call method', { connectId });
|
|
@@ -336,6 +347,20 @@ const waitForPendingPromise = async (
|
|
|
336
347
|
}
|
|
337
348
|
};
|
|
338
349
|
|
|
350
|
+
export function getPostCallPendingPromise(method: BaseMethod, device: Device): Promise<void> {
|
|
351
|
+
const cleanupPromise = device.waitForRunCleanup();
|
|
352
|
+
const env = DataManager.getSettings('env');
|
|
353
|
+
const deviceType = device.getCurrentDeviceType();
|
|
354
|
+
const requiresSigningCooldown =
|
|
355
|
+
method.name.includes('Sign') &&
|
|
356
|
+
(deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo) &&
|
|
357
|
+
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'node-usb');
|
|
358
|
+
|
|
359
|
+
return requiresSigningCooldown
|
|
360
|
+
? cleanupPromise.then(() => wait(PRO2_USB_SIGNING_COOLDOWN_MS)).then(() => undefined)
|
|
361
|
+
: cleanupPromise;
|
|
362
|
+
}
|
|
363
|
+
|
|
339
364
|
const onCallDevice = async (
|
|
340
365
|
context: CoreContext,
|
|
341
366
|
message: CoreMessage,
|
|
@@ -366,42 +391,57 @@ const onCallDevice = async (
|
|
|
366
391
|
DevicePool.clearDeviceCache(method.payload.connectId);
|
|
367
392
|
}
|
|
368
393
|
|
|
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
|
-
|
|
394
|
+
// Register before waiting so cancellation also covers queued requests.
|
|
380
395
|
const task = requestQueue.createTask(method);
|
|
381
396
|
|
|
382
397
|
// Pre-warm holds the device as a per-connectId callback task so a concurrent
|
|
383
398
|
// real call waits (before ensureConnected) instead of racing its Initialize.
|
|
384
399
|
// Only covers pre-warm -> real-call ordering; the reverse is fail-closed.
|
|
385
400
|
let preWarmCallbackTask: Deferred<void> | undefined;
|
|
386
|
-
if (method.isPreWarmSignal && method.connectId) {
|
|
387
|
-
preWarmCallbackTask = createDeferred<void>();
|
|
388
|
-
context.registerCallbackTask(method.connectId, preWarmCallbackTask);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
401
|
let device: Device;
|
|
392
402
|
try {
|
|
403
|
+
const connectId = method.connectId ?? '';
|
|
404
|
+
if (connectId) {
|
|
405
|
+
await requestQueue.waitForTask(task, () => context.waitForCallbackTasks(connectId));
|
|
406
|
+
}
|
|
407
|
+
await requestQueue.waitForTask(task, () =>
|
|
408
|
+
waitForPendingPromise(
|
|
409
|
+
method.connectId ?? '',
|
|
410
|
+
getPrePendingCallPromise,
|
|
411
|
+
removePrePendingCallPromise
|
|
412
|
+
)
|
|
413
|
+
);
|
|
414
|
+
if (method.isPreWarmSignal && method.connectId) {
|
|
415
|
+
preWarmCallbackTask = createDeferred<void>();
|
|
416
|
+
context.registerCallbackTask(method.connectId, preWarmCallbackTask);
|
|
417
|
+
}
|
|
393
418
|
/**
|
|
394
419
|
* Polling to ensure successful connection
|
|
395
420
|
*/
|
|
396
|
-
const connectId = method.connectId ?? '';
|
|
397
421
|
const pollingId = pollingManager.start(connectId);
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
method,
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
422
|
+
const env = DataManager.getSettings('env');
|
|
423
|
+
const connect = () =>
|
|
424
|
+
ensureConnected(context, method, connectId, pollingId, method.abortSignal);
|
|
425
|
+
// Discovery may acquire USB endpoints. Finish it before initializing a public
|
|
426
|
+
// request; once registered, that request makes discovery use cached state only.
|
|
427
|
+
if (DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env)) {
|
|
428
|
+
// Synchronization can keep the connect action queued after the caller is
|
|
429
|
+
// cancelled. Observe that promise so a late device-not-found error does
|
|
430
|
+
// not become an unhandled rejection.
|
|
431
|
+
const connectPromise = context.methodSynchronize(async () => {
|
|
432
|
+
if (method.abortSignal?.aborted) {
|
|
433
|
+
throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
434
|
+
}
|
|
435
|
+
return connect();
|
|
436
|
+
}, 'webusb-discovery');
|
|
437
|
+
connectPromise.catch(() => undefined);
|
|
438
|
+
device = await requestQueue.waitForTask(task, () => connectPromise);
|
|
439
|
+
} else {
|
|
440
|
+
device = await connect();
|
|
441
|
+
}
|
|
442
|
+
if (method.abortSignal?.aborted) {
|
|
443
|
+
throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
444
|
+
}
|
|
405
445
|
} catch (e) {
|
|
406
446
|
preWarmCallbackTask?.resolve();
|
|
407
447
|
Log.debug('ensureConnected error: ', e);
|
|
@@ -477,14 +517,19 @@ const onCallDevice = async (
|
|
|
477
517
|
|
|
478
518
|
try {
|
|
479
519
|
// Wait for any pending task except our own (self-wait would deadlock).
|
|
480
|
-
|
|
481
|
-
|
|
520
|
+
const { connectId } = method;
|
|
521
|
+
if (connectId) {
|
|
522
|
+
await requestQueue.waitForTask(task, () =>
|
|
523
|
+
context.waitForCallbackTasks(connectId, preWarmCallbackTask)
|
|
524
|
+
);
|
|
482
525
|
}
|
|
483
526
|
|
|
484
|
-
await
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
527
|
+
await requestQueue.waitForTask(task, () =>
|
|
528
|
+
waitForPendingPromise(
|
|
529
|
+
method.connectId ?? '',
|
|
530
|
+
getPrePendingCallPromise,
|
|
531
|
+
removePrePendingCallPromise
|
|
532
|
+
)
|
|
488
533
|
);
|
|
489
534
|
|
|
490
535
|
const inner = async (): Promise<void> => {
|
|
@@ -689,6 +734,13 @@ const onCallDevice = async (
|
|
|
689
734
|
},
|
|
690
735
|
});
|
|
691
736
|
messageResponse = createResponseMessage(method.responseID, true, response);
|
|
737
|
+
// Preserve the acknowledged result while the next call waits for cleanup and cooldown.
|
|
738
|
+
if (method.connectId) {
|
|
739
|
+
context.setPrePendingCallPromise(
|
|
740
|
+
method.connectId,
|
|
741
|
+
getPostCallPendingPromise(method, device)
|
|
742
|
+
);
|
|
743
|
+
}
|
|
692
744
|
requestQueue.resolveRequest(method.responseID, messageResponse);
|
|
693
745
|
completeMethodRequestContext(method);
|
|
694
746
|
} catch (error) {
|
|
@@ -700,6 +752,12 @@ const onCallDevice = async (
|
|
|
700
752
|
}
|
|
701
753
|
Log.debug(`Call API - Inner Method Run Error`, error);
|
|
702
754
|
messageResponse = createResponseMessage(method.responseID, false, { error });
|
|
755
|
+
if (method.connectId) {
|
|
756
|
+
context.setPrePendingCallPromise(
|
|
757
|
+
method.connectId,
|
|
758
|
+
getPostCallPendingPromise(method, device)
|
|
759
|
+
);
|
|
760
|
+
}
|
|
703
761
|
requestQueue.resolveRequest(method.responseID, messageResponse);
|
|
704
762
|
completeMethodRequestContext(method, error);
|
|
705
763
|
|
|
@@ -726,6 +784,9 @@ const onCallDevice = async (
|
|
|
726
784
|
skipInitialize: canSkipInitialize(method, device),
|
|
727
785
|
...parseInitOptions(method),
|
|
728
786
|
};
|
|
787
|
+
if (method.abortSignal?.aborted) {
|
|
788
|
+
throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
789
|
+
}
|
|
729
790
|
const deviceRun = () => device.run(inner, runOptions);
|
|
730
791
|
task.callPromise = createDeferred<any>(deviceRun);
|
|
731
792
|
|
|
@@ -746,6 +807,7 @@ const onCallDevice = async (
|
|
|
746
807
|
);
|
|
747
808
|
Log.debug('Call API - Run Error: ', error);
|
|
748
809
|
completeMethodRequestContext(method, error);
|
|
810
|
+
return messageResponse;
|
|
749
811
|
} finally {
|
|
750
812
|
// Release the pre-warm callback task so the next real call can proceed.
|
|
751
813
|
preWarmCallbackTask?.resolve();
|
|
@@ -945,7 +1007,16 @@ export function isRetryableBleConnectionError(method: BaseMethod, error: unknown
|
|
|
945
1007
|
if (method.device?.wasInterruptedByUser()) {
|
|
946
1008
|
return false;
|
|
947
1009
|
}
|
|
948
|
-
const typedError = error as {
|
|
1010
|
+
const typedError = error as {
|
|
1011
|
+
errorCode?: unknown;
|
|
1012
|
+
params?: { acquireDeadlineExceeded?: unknown };
|
|
1013
|
+
};
|
|
1014
|
+
if (
|
|
1015
|
+
typedError?.errorCode === HardwareErrorCode.BleTimeoutError &&
|
|
1016
|
+
typedError.params?.acquireDeadlineExceeded === true
|
|
1017
|
+
) {
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
949
1020
|
return (
|
|
950
1021
|
typedError?.errorCode === HardwareErrorCode.BleTimeoutError ||
|
|
951
1022
|
typedError?.errorCode === HardwareErrorCode.BleConnectedError ||
|
|
@@ -964,11 +1035,13 @@ export function isMissingDetectedProtocolV2Error(method: BaseMethod, error: unkn
|
|
|
964
1035
|
);
|
|
965
1036
|
}
|
|
966
1037
|
|
|
967
|
-
export function
|
|
1038
|
+
export function isTerminalBleStaleBondError(error: unknown) {
|
|
1039
|
+
return isBleStaleBondHardwareError(error);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
export function isDeviceIdentityMismatchError(error: unknown) {
|
|
968
1043
|
return (
|
|
969
|
-
|
|
970
|
-
(error as { errorCode?: unknown })?.errorCode ===
|
|
971
|
-
HardwareErrorCode.BlePeerRemovedPairingInformation
|
|
1044
|
+
(error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.DeviceCheckDeviceIdError
|
|
972
1045
|
);
|
|
973
1046
|
}
|
|
974
1047
|
|
|
@@ -1001,7 +1074,8 @@ function raceBleAcquire<T>(acquirePromise: Promise<T>, abortSignal?: AbortSignal
|
|
|
1001
1074
|
reject(
|
|
1002
1075
|
ERRORS.TypedError(
|
|
1003
1076
|
HardwareErrorCode.BleTimeoutError,
|
|
1004
|
-
`BLE acquire exceeded ${BLE_ACQUIRE_DEADLINE_MS}ms deadline
|
|
1077
|
+
`BLE acquire exceeded ${BLE_ACQUIRE_DEADLINE_MS}ms deadline`,
|
|
1078
|
+
{ acquireDeadlineExceeded: true }
|
|
1005
1079
|
)
|
|
1006
1080
|
)
|
|
1007
1081
|
),
|
|
@@ -1031,7 +1105,7 @@ async function connectDeviceForBle(
|
|
|
1031
1105
|
retryCount = 0
|
|
1032
1106
|
) {
|
|
1033
1107
|
try {
|
|
1034
|
-
if (device.wasInterruptedByUser()) {
|
|
1108
|
+
if (abortSignal?.aborted || device.wasInterruptedByUser()) {
|
|
1035
1109
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
1036
1110
|
}
|
|
1037
1111
|
if (method.payload.forceProtocolDetection && device.hasDeviceAcquire()) {
|
|
@@ -1100,6 +1174,9 @@ async function connectDeviceForBle(
|
|
|
1100
1174
|
DevicePool.emitter.emit(DEVICE.CONNECT, device);
|
|
1101
1175
|
}
|
|
1102
1176
|
} catch (err) {
|
|
1177
|
+
if (abortSignal?.aborted || device.wasInterruptedByUser()) {
|
|
1178
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
1179
|
+
}
|
|
1103
1180
|
const requiresColdReconnect = isMissingDetectedProtocolV2Error(method, err);
|
|
1104
1181
|
// Device.run()'s REQUIRE_DISCONNECT handling never sees acquire/initialize
|
|
1105
1182
|
// failures, so with keep-alive a wedged link would be reused by every retry
|
|
@@ -1150,6 +1227,7 @@ const ensureConnected = async (
|
|
|
1150
1227
|
const POLL_INTERVAL_TIME = (method.payload && method.payload.pollIntervalTime) || 1000;
|
|
1151
1228
|
const TIME_OUT = (method.payload && method.payload.timeout) || 10000;
|
|
1152
1229
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
1230
|
+
let lastInitializeError: any;
|
|
1153
1231
|
Log.debug(
|
|
1154
1232
|
`EnsureConnected function start, MAX_RETRY_COUNT=${MAX_RETRY_COUNT}, POLL_INTERVAL_TIME=${POLL_INTERVAL_TIME} `
|
|
1155
1233
|
);
|
|
@@ -1190,7 +1268,9 @@ const ensureConnected = async (
|
|
|
1190
1268
|
Log.debug('EnsureConnected function try count: ', tryCount, ' poll interval time: ', time);
|
|
1191
1269
|
try {
|
|
1192
1270
|
await initDeviceList(method);
|
|
1271
|
+
lastInitializeError = undefined;
|
|
1193
1272
|
} catch (error) {
|
|
1273
|
+
lastInitializeError = error;
|
|
1194
1274
|
Log.debug('device list error: ', error);
|
|
1195
1275
|
if (
|
|
1196
1276
|
[
|
|
@@ -1205,6 +1285,7 @@ const ensureConnected = async (
|
|
|
1205
1285
|
}
|
|
1206
1286
|
if (error.errorCode === HardwareErrorCode.TransportNotConfigured) {
|
|
1207
1287
|
await TransportManager.configure();
|
|
1288
|
+
lastInitializeError = undefined;
|
|
1208
1289
|
}
|
|
1209
1290
|
}
|
|
1210
1291
|
|
|
@@ -1273,6 +1354,8 @@ const ensureConnected = async (
|
|
|
1273
1354
|
HardwareErrorCode.BleDeviceBondedCanceled,
|
|
1274
1355
|
HardwareErrorCode.BleCharacteristicNotifyError,
|
|
1275
1356
|
HardwareErrorCode.BleTimeoutError,
|
|
1357
|
+
// A transport setup reset must also stop this outer poll.
|
|
1358
|
+
HardwareErrorCode.PollingTimeout,
|
|
1276
1359
|
HardwareErrorCode.BleWriteCharacteristicError,
|
|
1277
1360
|
HardwareErrorCode.BleAlreadyConnected,
|
|
1278
1361
|
HardwareErrorCode.FirmwareUpdateLimitOneDevice,
|
|
@@ -1283,7 +1366,12 @@ const ensureConnected = async (
|
|
|
1283
1366
|
HardwareErrorCode.DeviceInterruptedFromUser,
|
|
1284
1367
|
HardwareErrorCode.CallQueueActionCancelled,
|
|
1285
1368
|
].includes(error.errorCode) ||
|
|
1286
|
-
|
|
1369
|
+
(env === 'react-native' &&
|
|
1370
|
+
[HardwareErrorCode.BleDeviceDisconnected, HardwareErrorCode.PollingTimeout].includes(
|
|
1371
|
+
error.errorCode
|
|
1372
|
+
)) ||
|
|
1373
|
+
isTerminalBleStaleBondError(error) ||
|
|
1374
|
+
isDeviceIdentityMismatchError(error)
|
|
1287
1375
|
) {
|
|
1288
1376
|
reject(error);
|
|
1289
1377
|
return;
|
|
@@ -1295,14 +1383,22 @@ const ensureConnected = async (
|
|
|
1295
1383
|
clearTimeout(timer);
|
|
1296
1384
|
}
|
|
1297
1385
|
Log.debug('EnsureConnected get to max try count, will return: ', tryCount);
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1386
|
+
const preserveWebUsbInitError =
|
|
1387
|
+
DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env);
|
|
1388
|
+
const needsPermissionPrompt =
|
|
1389
|
+
DataManager.isBrowserWebUsb(env) && !method.payload?.skipWebDevicePrompt;
|
|
1390
|
+
const fallbackError = needsPermissionPrompt
|
|
1391
|
+
? ERRORS.TypedError(HardwareErrorCode.WebDeviceNotFoundOrNeedsPermission)
|
|
1392
|
+
: ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
1393
|
+
const errorToReject =
|
|
1394
|
+
preserveWebUsbInitError && lastInitializeError ? lastInitializeError : fallbackError;
|
|
1395
|
+
// Only ask the host for a WebUSB grant when the failure is actually
|
|
1396
|
+
// "not found / needs permission". A preserved initialize error must
|
|
1397
|
+
// not fire that prompt with a different public code.
|
|
1398
|
+
if (needsPermissionPrompt && errorToReject === fallbackError) {
|
|
1301
1399
|
postMessage(createUiMessage(UI_REQUEST.WEB_DEVICE_PROMPT_ACCESS_PERMISSION));
|
|
1302
|
-
reject(ERRORS.TypedError(HardwareErrorCode.WebDeviceNotFoundOrNeedsPermission));
|
|
1303
|
-
} else {
|
|
1304
|
-
reject(ERRORS.TypedError(HardwareErrorCode.DeviceNotFound));
|
|
1305
1400
|
}
|
|
1401
|
+
reject(errorToReject);
|
|
1306
1402
|
return;
|
|
1307
1403
|
}
|
|
1308
1404
|
|
|
@@ -1374,10 +1470,11 @@ export const cancel = (context: CoreContext, connectId?: string) => {
|
|
|
1374
1470
|
}
|
|
1375
1471
|
} else {
|
|
1376
1472
|
const env = DataManager.getSettings('env');
|
|
1473
|
+
// Abort every method before rejecting its queue task. Non-BLE methods also
|
|
1474
|
+
// use the signal to stop recovery loops after the public promise is rejected.
|
|
1475
|
+
requestQueue.abortAllRequests();
|
|
1377
1476
|
if (DataManager.isBleConnect(env)) {
|
|
1378
1477
|
Log.debug('Cancel Api all _deviceList: ');
|
|
1379
|
-
// Keep method abort signals observable until every active task is rejected.
|
|
1380
|
-
requestQueue.abortAllRequests();
|
|
1381
1478
|
const canceledDevices: Device[] = [];
|
|
1382
1479
|
const interruptDevice = (device?: Device) => {
|
|
1383
1480
|
if (!device || canceledDevices.includes(device)) {
|
|
@@ -1759,7 +1856,17 @@ export default class Core extends EventEmitter {
|
|
|
1759
1856
|
this.prePendingCallPromises.delete(connectId);
|
|
1760
1857
|
return;
|
|
1761
1858
|
}
|
|
1762
|
-
this.prePendingCallPromises.
|
|
1859
|
+
const previous = this.prePendingCallPromises.get(connectId);
|
|
1860
|
+
const cleanupPromise =
|
|
1861
|
+
previous && previous !== promise
|
|
1862
|
+
? Promise.all([previous, promise]).then(() => undefined)
|
|
1863
|
+
: promise;
|
|
1864
|
+
this.prePendingCallPromises.set(connectId, cleanupPromise);
|
|
1865
|
+
// cancel() is fire-and-forget. Observe failures immediately, while
|
|
1866
|
+
// preserving the rejected barrier for the next caller's safety check.
|
|
1867
|
+
cleanupPromise.catch(error => {
|
|
1868
|
+
Log.warn('Device cancellation cleanup failed', { errorCode: error?.errorCode });
|
|
1869
|
+
});
|
|
1763
1870
|
},
|
|
1764
1871
|
removePrePendingCallPromise: (connectId: string, promise: Promise<void>) => {
|
|
1765
1872
|
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
|
},
|
|
@@ -33,6 +33,8 @@ export default class TransportManager {
|
|
|
33
33
|
|
|
34
34
|
static reactNativeInit = false;
|
|
35
35
|
|
|
36
|
+
static webUsbInit = false;
|
|
37
|
+
|
|
36
38
|
static protocolV1MessageSchema: ProtocolV1MessageSchema = 'v1CurrentSchema';
|
|
37
39
|
|
|
38
40
|
static plugin: LowlevelTransportSharedPlugin | null = null;
|
|
@@ -42,6 +44,17 @@ export default class TransportManager {
|
|
|
42
44
|
this.defaultMessages = DataManager.getProtobufMessages();
|
|
43
45
|
this.currentMessages = this.defaultMessages;
|
|
44
46
|
this.protocolV1MessageSchema = 'v1CurrentSchema';
|
|
47
|
+
this.webUsbInit = false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static async ensureInitialized() {
|
|
51
|
+
const env = DataManager.getSettings('env');
|
|
52
|
+
if (env !== 'webusb' && env !== 'desktop-webusb') return;
|
|
53
|
+
if (this.webUsbInit) return;
|
|
54
|
+
// The emitter registers USB disconnect events; this must happen even when
|
|
55
|
+
// schema configuration is intentionally deferred during discovery.
|
|
56
|
+
await this.transport.init(WebUsbLogger, DevicePool.emitter);
|
|
57
|
+
this.webUsbInit = true;
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
static async configure() {
|
|
@@ -69,9 +82,7 @@ export default class TransportManager {
|
|
|
69
82
|
} else if (env === 'desktop-web-ble') {
|
|
70
83
|
await this.transport.init(WebBleLogger, DevicePool.emitter);
|
|
71
84
|
} else if (env === 'webusb' || env === 'desktop-webusb') {
|
|
72
|
-
|
|
73
|
-
// DEVICE.DISCONNECT; without it WebUSB never reports device removal.
|
|
74
|
-
await this.transport.init(WebUsbLogger, DevicePool.emitter);
|
|
85
|
+
await this.ensureInitialized();
|
|
75
86
|
} else {
|
|
76
87
|
await this.transport.init(HttpLogger);
|
|
77
88
|
}
|