@onekeyfe/hd-core 1.2.0-alpha.98 → 1.2.0
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 +20 -4
- package/__tests__/DeviceCommands.test.ts +203 -27
- package/__tests__/base64Data.test.ts +69 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +89 -38
- package/__tests__/check-firmware-release-protocol-v2.test.ts +83 -14
- package/__tests__/device-connector-protocol.test.ts +21 -0
- package/__tests__/device-lifecycle-events.test.ts +427 -7
- package/__tests__/device-pool-state.test.ts +25 -0
- package/__tests__/device-settings.test.ts +237 -9
- package/__tests__/device-state-events.test.ts +2 -2
- package/__tests__/device-state-mapper.test.ts +61 -5
- package/__tests__/device-utils.test.ts +15 -1
- package/__tests__/device-wallet-session-store.test.ts +147 -21
- package/__tests__/deviceSettings.test.ts +7 -3
- package/__tests__/deviceUploadNft.test.ts +36 -4
- package/__tests__/firmware-update/device-update-bootloader.test.ts +14 -3
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +61 -9
- package/__tests__/firmware-update/firmware-prepared-host-digest.test.ts +19 -9
- package/__tests__/firmware-update/firmware-prepared-resource-archive.test.ts +21 -0
- package/__tests__/firmware-update/firmware-update-bootloader-poll.test.ts +128 -2
- package/__tests__/firmware-update/firmware-update-plan-bootloader-identity.test.ts +80 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +114 -50
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +19 -5
- package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +116 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +678 -0
- package/__tests__/firmware-update/firmware-update-v4-parameters.test.ts +85 -0
- package/__tests__/get-device-state.test.ts +60 -21
- package/__tests__/logBlockEvent.test.ts +13 -1
- package/__tests__/open-wallet-session.test.ts +127 -1
- package/__tests__/protocol-v2-firmware-targets.test.ts +39 -0
- package/__tests__/protocol-v2-resources.test.ts +185 -88
- package/__tests__/protocol-v2-unlock-policy.test.ts +235 -2
- package/__tests__/protocol-v2.test.ts +2274 -466
- package/__tests__/refresh-device-state.test.ts +3 -1
- package/__tests__/resourceBase64Boundary.test.ts +48 -0
- package/__tests__/search-devices.test.ts +79 -32
- package/__tests__/ton-sign-message.test.ts +84 -0
- package/dist/api/BaseMethod.d.ts +2 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +22 -7
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts +1 -1
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
- package/dist/api/device/DeviceRebootToBoardloader.d.ts.map +1 -1
- package/dist/api/device/DeviceRebootToBootloader.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/device/DeviceVerify.d.ts +1 -0
- package/dist/api/device/DeviceVerify.d.ts.map +1 -1
- package/dist/api/device/DeviceWipe.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +5 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -1
- package/dist/api/firmware/FirmwarePreparedResourceArchive.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -15
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +3 -2
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
- package/dist/api/helpers/base64Data.d.ts +16 -0
- package/dist/api/helpers/base64Data.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +3 -1
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceReboot.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
- package/dist/core/index.d.ts +6 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +4 -3
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/TransportManager.d.ts.map +1 -1
- package/dist/device/Device.d.ts +16 -3
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +1 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +3 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +54 -84
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2333 -1515
- package/dist/protocols/protocol-v2/features.d.ts +13 -5
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +2 -2
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +12 -7
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +3 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdatePlan.d.ts +2 -2
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +1 -1
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/settings.d.ts +3 -31
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/deviceSettings.d.ts +1 -0
- package/dist/utils/deviceSettings.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 +7 -0
- package/dist/utils/pro2Nft.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/api/BaseMethod.ts +8 -1
- package/src/api/CheckAllFirmwareRelease.ts +8 -5
- package/src/api/CheckBLEFirmwareRelease.ts +2 -2
- package/src/api/CheckBootloaderRelease.ts +3 -2
- package/src/api/CheckFirmwareRelease.ts +2 -2
- package/src/api/FirmwareUpdateV2.ts +130 -69
- package/src/api/FirmwareUpdateV3.ts +29 -19
- package/src/api/FirmwareUpdateV4.ts +966 -743
- package/src/api/OpenWalletSession.ts +39 -9
- package/src/api/PromptWebDeviceAccess.ts +2 -2
- package/src/api/SearchDevices.ts +6 -3
- package/src/api/UploadPortfolio.ts +9 -2
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +2 -1
- package/src/api/device/DeviceChangePin.ts +4 -1
- package/src/api/device/DeviceRebootToBoardloader.ts +3 -1
- package/src/api/device/DeviceRebootToBootloader.ts +3 -1
- package/src/api/device/DeviceSettings.ts +34 -20
- package/src/api/device/DeviceVerify.ts +25 -0
- package/src/api/device/DeviceWipe.ts +4 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +41 -14
- package/src/api/firmware/FirmwareHostBinding.ts +10 -0
- package/src/api/firmware/FirmwarePreparedResourceArchive.ts +16 -27
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +131 -76
- package/src/api/firmware/FirmwareUpdatePlan.ts +55 -82
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +32 -15
- package/src/api/firmware/protocolV2Release.ts +7 -2
- package/src/api/helpers/base64Data.ts +85 -0
- package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
- package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +6 -28
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
- package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
- package/src/api/protocol-v2/DeviceReboot.ts +5 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +61 -9
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +53 -19
- package/src/api/protocol-v2/helpers.ts +8 -0
- package/src/api/ton/TonSignMessage.ts +5 -3
- package/src/core/index.ts +264 -55
- package/src/data/messages/messages-protocol-v2.json +140 -13
- package/src/data-manager/DataManager.ts +46 -39
- package/src/data-manager/TransportManager.ts +3 -1
- package/src/device/Device.ts +210 -44
- package/src/device/DeviceCommands.ts +27 -18
- package/src/device/DeviceConnector.ts +17 -3
- package/src/device/DevicePool.ts +9 -2
- package/src/device/DeviceStateMapper.ts +30 -24
- package/src/deviceProfile/buildDeviceFeatures.ts +10 -5
- package/src/events/logBlockEvent.ts +14 -1
- package/src/events/ui-request.ts +3 -0
- package/src/index.ts +0 -10
- package/src/protocols/protocol-v2/features.ts +22 -5
- package/src/protocols/protocol-v2/index.ts +2 -0
- package/src/protocols/protocol-v2/resources.ts +110 -147
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +13 -1
- package/src/protocols/protocol-v2/walletSession.ts +65 -22
- package/src/types/api/firmwareUpdate.ts +1 -1
- package/src/types/api/firmwareUpdatePlan.ts +2 -2
- package/src/types/api/protocolV2.ts +1 -1
- package/src/types/device.ts +2 -1
- package/src/types/settings.ts +6 -34
- package/src/utils/deviceFeaturesCompat.ts +9 -4
- package/src/utils/deviceFeaturesUtils.ts +4 -0
- package/src/utils/deviceInfoUtils.ts +9 -2
- package/src/utils/deviceSettings.ts +11 -4
- package/src/utils/index.ts +1 -0
- package/src/utils/pro2Nft.ts +31 -8
- package/__tests__/firmware-memory-host.test.ts +0 -112
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +0 -22
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +0 -1
- package/src/api/firmware/FirmwareMemoryHost.ts +0 -142
package/src/core/index.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import semver from 'semver';
|
|
2
2
|
import EventEmitter from 'events';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
DeviceSessionPinType,
|
|
5
|
+
TRANSPORT_EVENT,
|
|
6
|
+
isProtocolV2LinkDisabledError,
|
|
7
|
+
} from '@onekeyfe/hd-transport';
|
|
4
8
|
import {
|
|
5
9
|
ERRORS,
|
|
6
10
|
ERROR_CODES_REQUIRE_DISCONNECT,
|
|
@@ -79,6 +83,7 @@ import type { BaseMethod } from '../api/BaseMethod';
|
|
|
79
83
|
|
|
80
84
|
const Log = getLogger(LoggerNames.Core);
|
|
81
85
|
const PRE_INITIALIZE_TTL_MS = 60 * 1000;
|
|
86
|
+
const PRE_PENDING_CALL_TIMEOUT_MS = 15 * 1000;
|
|
82
87
|
|
|
83
88
|
// Dedup/coalesce state for "pre-warm signal" methods (isPreWarmSignal),
|
|
84
89
|
// keyed by getPreWarmKey(): coalesce in-flight, skip if warmed within TTL.
|
|
@@ -136,6 +141,9 @@ const toError = (error: unknown): Error | undefined => {
|
|
|
136
141
|
}
|
|
137
142
|
};
|
|
138
143
|
|
|
144
|
+
const isExpectedCompatibilityError = (error: unknown) =>
|
|
145
|
+
error instanceof HardwareError && error.errorCode === HardwareErrorCode.DeviceNotSupportMethod;
|
|
146
|
+
|
|
139
147
|
const updateMethodRequestContext = (method: BaseMethod, updates: any) => {
|
|
140
148
|
if (method.requestContext) {
|
|
141
149
|
updateRequestContext(method.requestContext.responseID, updates);
|
|
@@ -289,26 +297,35 @@ const handlePreWarmSignal = async (
|
|
|
289
297
|
}
|
|
290
298
|
};
|
|
291
299
|
|
|
292
|
-
const waitWithTimeout = async (promise: Promise<any>, timeout: number) => {
|
|
293
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
294
|
-
setTimeout(() => reject(new Error('Request timeout')), timeout);
|
|
295
|
-
});
|
|
296
|
-
return Promise.race([promise, timeoutPromise]);
|
|
297
|
-
};
|
|
298
|
-
|
|
299
300
|
const waitForPendingPromise = async (
|
|
300
|
-
|
|
301
|
-
|
|
301
|
+
connectId: string,
|
|
302
|
+
getPrePendingCallPromise: (connectId: string) => Promise<void> | undefined,
|
|
303
|
+
removePrePendingCallPromise?: (connectId: string, promise: Promise<void>) => void
|
|
302
304
|
) => {
|
|
303
|
-
const pendingPromise = getPrePendingCallPromise();
|
|
305
|
+
const pendingPromise = getPrePendingCallPromise(connectId);
|
|
304
306
|
if (pendingPromise) {
|
|
305
307
|
Log.debug('pre pending call promise before call method, wait for it');
|
|
308
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
309
|
+
let timedOut = false;
|
|
306
310
|
try {
|
|
307
|
-
await
|
|
311
|
+
await Promise.race([
|
|
312
|
+
pendingPromise,
|
|
313
|
+
new Promise<void>(resolve => {
|
|
314
|
+
timer = setTimeout(() => {
|
|
315
|
+
timedOut = true;
|
|
316
|
+
resolve();
|
|
317
|
+
}, PRE_PENDING_CALL_TIMEOUT_MS);
|
|
318
|
+
}),
|
|
319
|
+
]);
|
|
308
320
|
} catch (error) {
|
|
309
|
-
//
|
|
321
|
+
// Cancellation is best-effort; the transport teardown owns recovery.
|
|
322
|
+
} finally {
|
|
323
|
+
if (timer) clearTimeout(timer);
|
|
324
|
+
removePrePendingCallPromise?.(connectId, pendingPromise);
|
|
325
|
+
}
|
|
326
|
+
if (timedOut) {
|
|
327
|
+
Log.warn('pre pending call promise timed out before call method', { connectId });
|
|
310
328
|
}
|
|
311
|
-
removePrePendingCallPromise?.(pendingPromise);
|
|
312
329
|
Log.debug('pre pending call promise before call method done');
|
|
313
330
|
}
|
|
314
331
|
};
|
|
@@ -320,7 +337,7 @@ const onCallDevice = async (
|
|
|
320
337
|
): Promise<any> => {
|
|
321
338
|
let messageResponse: any;
|
|
322
339
|
|
|
323
|
-
const { requestQueue, getPrePendingCallPromise,
|
|
340
|
+
const { requestQueue, getPrePendingCallPromise, removePrePendingCallPromise } = context;
|
|
324
341
|
|
|
325
342
|
updateMethodRequestContext(method, { status: 'running' });
|
|
326
343
|
|
|
@@ -348,7 +365,11 @@ const onCallDevice = async (
|
|
|
348
365
|
await context.waitForCallbackTasks(method.connectId);
|
|
349
366
|
}
|
|
350
367
|
|
|
351
|
-
await waitForPendingPromise(
|
|
368
|
+
await waitForPendingPromise(
|
|
369
|
+
method.connectId ?? '',
|
|
370
|
+
getPrePendingCallPromise,
|
|
371
|
+
removePrePendingCallPromise
|
|
372
|
+
);
|
|
352
373
|
|
|
353
374
|
const task = requestQueue.createTask(method);
|
|
354
375
|
|
|
@@ -394,6 +415,13 @@ const onCallDevice = async (
|
|
|
394
415
|
if (method.payload?.onlyConnectBleDevice) {
|
|
395
416
|
preWarmCallbackTask?.resolve();
|
|
396
417
|
Log.debug('Call API - only connect ble device: ', device?.mainId);
|
|
418
|
+
// This early return bypasses the normal-path bookkeeping at the end of the
|
|
419
|
+
// call. Without it the task leaks and haunts every later queue snapshot
|
|
420
|
+
// and cancel sweep (field log: a completed task lingered for 6 minutes),
|
|
421
|
+
// and the request stays in the active maps, so repeated preconnects pile
|
|
422
|
+
// up phantom work in diagnostics.
|
|
423
|
+
completeMethodRequestContext(method);
|
|
424
|
+
requestQueue.releaseTask(method.responseID);
|
|
397
425
|
return createResponseMessage(method.responseID, true, null);
|
|
398
426
|
}
|
|
399
427
|
|
|
@@ -447,7 +475,11 @@ const onCallDevice = async (
|
|
|
447
475
|
await context.waitForCallbackTasks(method.connectId, preWarmCallbackTask);
|
|
448
476
|
}
|
|
449
477
|
|
|
450
|
-
await waitForPendingPromise(
|
|
478
|
+
await waitForPendingPromise(
|
|
479
|
+
method.connectId ?? '',
|
|
480
|
+
getPrePendingCallPromise,
|
|
481
|
+
removePrePendingCallPromise
|
|
482
|
+
);
|
|
451
483
|
|
|
452
484
|
const inner = async (): Promise<void> => {
|
|
453
485
|
// Protocol is established from an active device response during acquire/initialize.
|
|
@@ -619,7 +651,8 @@ const onCallDevice = async (
|
|
|
619
651
|
method.payload?.passphraseState,
|
|
620
652
|
method.payload?.useEmptyPassphrase,
|
|
621
653
|
method.payload?.skipPassphraseCheck,
|
|
622
|
-
hasDeriveCardano(method)
|
|
654
|
+
hasDeriveCardano(method),
|
|
655
|
+
method.protocolV2UnlockContext?.preflightMainPinSelected
|
|
623
656
|
);
|
|
624
657
|
|
|
625
658
|
// Double check, handles the special case of Touch/Pro
|
|
@@ -693,7 +726,9 @@ const onCallDevice = async (
|
|
|
693
726
|
try {
|
|
694
727
|
return await task.callPromise.promise;
|
|
695
728
|
} catch (e) {
|
|
696
|
-
|
|
729
|
+
if (!isExpectedCompatibilityError(e)) {
|
|
730
|
+
Log.debug('Device Run Error: ', e);
|
|
731
|
+
}
|
|
697
732
|
completeMethodRequestContext(method, e);
|
|
698
733
|
return createResponseMessage(method.responseID, false, { error: e });
|
|
699
734
|
}
|
|
@@ -887,22 +922,112 @@ function canSkipInitialize(method: BaseMethod, device: Device): boolean {
|
|
|
887
922
|
return true;
|
|
888
923
|
}
|
|
889
924
|
|
|
890
|
-
function isRetryableBleProtocolV2ProbeError(method: BaseMethod, error: unknown) {
|
|
891
|
-
const
|
|
925
|
+
export function isRetryableBleProtocolV2ProbeError(method: BaseMethod, error: unknown) {
|
|
926
|
+
const typedError = error as { errorCode?: unknown; message?: unknown };
|
|
927
|
+
const message =
|
|
928
|
+
typeof typedError?.message === 'string' ? typedError.message : String(error ?? '');
|
|
892
929
|
return (
|
|
893
930
|
method.payload.connectProtocol === 'V2' &&
|
|
931
|
+
typedError?.errorCode === HardwareErrorCode.RuntimeError &&
|
|
894
932
|
message.includes('Device protocol mismatch') &&
|
|
895
933
|
message.includes('expected V2') &&
|
|
896
934
|
message.includes('did not respond to expected protocol')
|
|
897
935
|
);
|
|
898
936
|
}
|
|
899
937
|
|
|
938
|
+
export function isRetryableBleConnectionError(method: BaseMethod, error: unknown) {
|
|
939
|
+
if (method.device?.wasInterruptedByUser()) {
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
const typedError = error as { errorCode?: unknown };
|
|
943
|
+
return (
|
|
944
|
+
typedError?.errorCode === HardwareErrorCode.BleTimeoutError ||
|
|
945
|
+
typedError?.errorCode === HardwareErrorCode.BleConnectedError ||
|
|
946
|
+
isRetryableBleProtocolV2ProbeError(method, error) ||
|
|
947
|
+
isMissingDetectedProtocolV2Error(method, error)
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
export function isMissingDetectedProtocolV2Error(method: BaseMethod, error: unknown) {
|
|
952
|
+
const typedError = error as { errorCode?: unknown; message?: unknown };
|
|
953
|
+
return (
|
|
954
|
+
method.payload.connectProtocol === 'V2' &&
|
|
955
|
+
typedError?.errorCode === HardwareErrorCode.RuntimeError &&
|
|
956
|
+
typeof typedError.message === 'string' &&
|
|
957
|
+
typedError.message.includes('Device protocol has not been detected')
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
export function isProtocolV2PeerRemovedPairingError(method: BaseMethod, error: unknown) {
|
|
962
|
+
return (
|
|
963
|
+
method.payload.connectProtocol === 'V2' &&
|
|
964
|
+
(error as { errorCode?: unknown })?.errorCode ===
|
|
965
|
+
HardwareErrorCode.BlePeerRemovedPairingInformation
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
|
|
900
969
|
/**
|
|
901
970
|
* If the Bluetooth connection times out, retry up to 6 times
|
|
902
971
|
* @param retryCount - Current retry count (default 0)
|
|
903
972
|
*/
|
|
904
|
-
|
|
973
|
+
// device.acquire awaits a transport reply with no deadline of its own; a
|
|
974
|
+
// transport that never settles (field case: Electron main lost an IPC reply,
|
|
975
|
+
// "reply was never sent" after 5 minutes) hangs the call forever and cancel()
|
|
976
|
+
// only takes effect at poll checkpoints. Race acquire against a deadline and
|
|
977
|
+
// the caller's abort signal so the hang is bounded and cancel is immediate.
|
|
978
|
+
const BLE_ACQUIRE_DEADLINE_MS = 60 * 1000;
|
|
979
|
+
|
|
980
|
+
function raceBleAcquire<T>(acquirePromise: Promise<T>, abortSignal?: AbortSignal): Promise<T> {
|
|
981
|
+
return new Promise<T>((resolve, reject) => {
|
|
982
|
+
let settled = false;
|
|
983
|
+
const settle = (fn: () => void) => {
|
|
984
|
+
if (settled) return;
|
|
985
|
+
settled = true;
|
|
986
|
+
clearTimeout(deadline);
|
|
987
|
+
abortSignal?.removeEventListener('abort', onAbort);
|
|
988
|
+
fn();
|
|
989
|
+
};
|
|
990
|
+
const onAbort = () =>
|
|
991
|
+
settle(() => reject(ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled)));
|
|
992
|
+
const deadline = setTimeout(
|
|
993
|
+
() =>
|
|
994
|
+
settle(() =>
|
|
995
|
+
reject(
|
|
996
|
+
ERRORS.TypedError(
|
|
997
|
+
HardwareErrorCode.BleTimeoutError,
|
|
998
|
+
`BLE acquire exceeded ${BLE_ACQUIRE_DEADLINE_MS}ms deadline`
|
|
999
|
+
)
|
|
1000
|
+
)
|
|
1001
|
+
),
|
|
1002
|
+
BLE_ACQUIRE_DEADLINE_MS
|
|
1003
|
+
);
|
|
1004
|
+
// Attach before any early return so a late settlement of acquirePromise
|
|
1005
|
+
// is always consumed — an abort or deadline must never leave the acquire
|
|
1006
|
+
// rejection unhandled.
|
|
1007
|
+
acquirePromise.then(
|
|
1008
|
+
value => settle(() => resolve(value)),
|
|
1009
|
+
error => settle(() => reject(error))
|
|
1010
|
+
);
|
|
1011
|
+
if (abortSignal) {
|
|
1012
|
+
if (abortSignal.aborted) {
|
|
1013
|
+
onAbort();
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
abortSignal.addEventListener('abort', onAbort);
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
async function connectDeviceForBle(
|
|
1022
|
+
method: BaseMethod,
|
|
1023
|
+
device: Device,
|
|
1024
|
+
abortSignal?: AbortSignal,
|
|
1025
|
+
retryCount = 0
|
|
1026
|
+
) {
|
|
905
1027
|
try {
|
|
1028
|
+
if (device.wasInterruptedByUser()) {
|
|
1029
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
1030
|
+
}
|
|
906
1031
|
if (method.payload.forceProtocolDetection && device.hasDeviceAcquire()) {
|
|
907
1032
|
await device.release();
|
|
908
1033
|
}
|
|
@@ -912,9 +1037,43 @@ async function connectDeviceForBle(method: BaseMethod, device: Device, retryCoun
|
|
|
912
1037
|
!device.commands ||
|
|
913
1038
|
device.commands.disposed;
|
|
914
1039
|
if (shouldAcquire) {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1040
|
+
// The deadline/abort guards are scoped to the desktop electron
|
|
1041
|
+
// transport: its IPC acquire is the only path with a proven
|
|
1042
|
+
// never-settling failure mode, while react-native/lowlevel acquire may
|
|
1043
|
+
// legitimately block on a user-driven system bonding prompt for longer
|
|
1044
|
+
// than any sane deadline. Other envs keep the plain acquire unchanged.
|
|
1045
|
+
const useAcquireGuards = DataManager.getSettings('env') === 'desktop-web-ble';
|
|
1046
|
+
// A cancel landing during the retry backoff must not start a new acquire.
|
|
1047
|
+
if (useAcquireGuards && abortSignal?.aborted) {
|
|
1048
|
+
throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
1049
|
+
}
|
|
1050
|
+
if (!useAcquireGuards) {
|
|
1051
|
+
await device.acquire(method.payload.connectProtocol, {
|
|
1052
|
+
forceProtocolDetection: method.payload.forceProtocolDetection,
|
|
1053
|
+
});
|
|
1054
|
+
} else {
|
|
1055
|
+
try {
|
|
1056
|
+
await raceBleAcquire(
|
|
1057
|
+
device.acquire(method.payload.connectProtocol, {
|
|
1058
|
+
forceProtocolDetection: method.payload.forceProtocolDetection,
|
|
1059
|
+
}),
|
|
1060
|
+
abortSignal
|
|
1061
|
+
);
|
|
1062
|
+
} catch (err) {
|
|
1063
|
+
// A deadline hit means the transport is wedged mid-acquire; drop the
|
|
1064
|
+
// link before the retry so it cold-connects instead of stacking a
|
|
1065
|
+
// second connect onto the half-open one.
|
|
1066
|
+
if (
|
|
1067
|
+
err.errorCode === HardwareErrorCode.BleTimeoutError &&
|
|
1068
|
+
device.mainId &&
|
|
1069
|
+
device.deviceConnector
|
|
1070
|
+
) {
|
|
1071
|
+
await device.deviceConnector.disconnect(device.mainId).catch(() => undefined);
|
|
1072
|
+
device.markTransportDisconnected();
|
|
1073
|
+
}
|
|
1074
|
+
throw err;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
918
1077
|
}
|
|
919
1078
|
if (method.payload?.onlyConnectBleDevice) {
|
|
920
1079
|
if (shouldAcquire) {
|
|
@@ -934,12 +1093,13 @@ async function connectDeviceForBle(method: BaseMethod, device: Device, retryCoun
|
|
|
934
1093
|
DevicePool.emitter.emit(DEVICE.CONNECT, device);
|
|
935
1094
|
}
|
|
936
1095
|
} catch (err) {
|
|
1096
|
+
const requiresColdReconnect = isMissingDetectedProtocolV2Error(method, err);
|
|
937
1097
|
// Device.run()'s REQUIRE_DISCONNECT handling never sees acquire/initialize
|
|
938
1098
|
// failures, so with keep-alive a wedged link would be reused by every retry
|
|
939
1099
|
// (field case: Initialize timing out at 25s per attempt, forever). Drop it
|
|
940
1100
|
// here so the next retry cold-connects.
|
|
941
1101
|
if (
|
|
942
|
-
ERROR_CODES_REQUIRE_DISCONNECT.includes(err.errorCode) &&
|
|
1102
|
+
(ERROR_CODES_REQUIRE_DISCONNECT.includes(err.errorCode) || requiresColdReconnect) &&
|
|
943
1103
|
device.mainId &&
|
|
944
1104
|
device.deviceConnector
|
|
945
1105
|
) {
|
|
@@ -949,16 +1109,11 @@ async function connectDeviceForBle(method: BaseMethod, device: Device, retryCoun
|
|
|
949
1109
|
// next attempt skip acquire and initialize onto the link we just cut.
|
|
950
1110
|
device.markTransportDisconnected();
|
|
951
1111
|
}
|
|
952
|
-
if (
|
|
953
|
-
(err.errorCode === HardwareErrorCode.BleTimeoutError ||
|
|
954
|
-
err.errorCode === HardwareErrorCode.BleConnectedError ||
|
|
955
|
-
isRetryableBleProtocolV2ProbeError(method, err)) &&
|
|
956
|
-
retryCount < 6
|
|
957
|
-
) {
|
|
1112
|
+
if (isRetryableBleConnectionError(method, err) && retryCount < 6) {
|
|
958
1113
|
const nextRetry = retryCount + 1;
|
|
959
|
-
Log.debug(`Bluetooth
|
|
1114
|
+
Log.debug(`Bluetooth connection will retry, retry count: ${nextRetry}`);
|
|
960
1115
|
await wait(3000);
|
|
961
|
-
await connectDeviceForBle(method, device, nextRetry);
|
|
1116
|
+
await connectDeviceForBle(method, device, abortSignal, nextRetry);
|
|
962
1117
|
} else {
|
|
963
1118
|
throw err;
|
|
964
1119
|
}
|
|
@@ -1063,13 +1218,31 @@ const ensureConnected = async (
|
|
|
1063
1218
|
if (abort()) {
|
|
1064
1219
|
return;
|
|
1065
1220
|
}
|
|
1066
|
-
|
|
1221
|
+
// One generation per public ensureConnected() call, not per poll
|
|
1222
|
+
// iteration. A later poll must not clear a user cancel.
|
|
1223
|
+
if (tryCount === 1) {
|
|
1224
|
+
device.beginConnectionAttempt();
|
|
1225
|
+
}
|
|
1226
|
+
await connectDeviceForBle(method, device, abortSignal);
|
|
1067
1227
|
}
|
|
1068
1228
|
resolve(device);
|
|
1069
1229
|
return;
|
|
1070
1230
|
}
|
|
1071
1231
|
} catch (error) {
|
|
1072
1232
|
Log.debug('device error: ', error);
|
|
1233
|
+
if (error.errorCode === HardwareErrorCode.BleUnavailableWhileUsbConnected) {
|
|
1234
|
+
reject(error);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
if (isProtocolV2LinkDisabledError(error)) {
|
|
1238
|
+
reject(
|
|
1239
|
+
ERRORS.TypedError(HardwareErrorCode.BleUnavailableWhileUsbConnected, undefined, {
|
|
1240
|
+
failureCode: error.failureCode,
|
|
1241
|
+
firmwareMessage: error.firmwareMessage,
|
|
1242
|
+
})
|
|
1243
|
+
);
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1073
1246
|
if ([HardwareErrorCode.BleCharacteristicNotifyChangeFailure].includes(error.errorCode)) {
|
|
1074
1247
|
postMessage(createUiMessage(UI_REQUEST.BLUETOOTH_CHARACTERISTIC_NOTIFY_CHANGE_FAILURE));
|
|
1075
1248
|
}
|
|
@@ -1092,7 +1265,10 @@ const ensureConnected = async (
|
|
|
1092
1265
|
HardwareErrorCode.DeviceDetectInBootloaderMode,
|
|
1093
1266
|
HardwareErrorCode.BleCharacteristicNotifyChangeFailure,
|
|
1094
1267
|
HardwareErrorCode.BridgeNeedsPermission,
|
|
1095
|
-
|
|
1268
|
+
HardwareErrorCode.DeviceInterruptedFromUser,
|
|
1269
|
+
HardwareErrorCode.CallQueueActionCancelled,
|
|
1270
|
+
].includes(error.errorCode) ||
|
|
1271
|
+
isProtocolV2PeerRemovedPairingError(method, error)
|
|
1096
1272
|
) {
|
|
1097
1273
|
reject(error);
|
|
1098
1274
|
return;
|
|
@@ -1148,23 +1324,34 @@ export const cancel = (context: CoreContext, connectId?: string) => {
|
|
|
1148
1324
|
','
|
|
1149
1325
|
)}`
|
|
1150
1326
|
);
|
|
1327
|
+
// Abort before rejecting: rejectRequest releases the task and would make
|
|
1328
|
+
// its AbortController unreachable to an in-flight method loop.
|
|
1329
|
+
requestQueue.abortRequestsByConnectId(connectId);
|
|
1151
1330
|
const canceledDevices: Device[] = [];
|
|
1331
|
+
const interruptDevice = (device: Device | undefined, deviceConnectId: string) => {
|
|
1332
|
+
if (!device || canceledDevices.includes(device)) {
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
setPrePendingCallPromise(deviceConnectId, device.interruptionFromUser());
|
|
1336
|
+
canceledDevices.push(device);
|
|
1337
|
+
};
|
|
1152
1338
|
for (const requestId of requestIds) {
|
|
1153
1339
|
const task = requestQueue.getTask(requestId);
|
|
1154
1340
|
Log.debug('Cancel Api connect task: ', task);
|
|
1155
|
-
if (task
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1341
|
+
if (task) {
|
|
1342
|
+
// During ensureConnected the method has a connectId but device is
|
|
1343
|
+
// assigned only after the poll succeeds. Interrupt the cached BLE
|
|
1344
|
+
// Device so an in-flight acquire/initialize cannot finish.
|
|
1345
|
+
interruptDevice(task.method?.device, task.method.connectId ?? connectId);
|
|
1346
|
+
interruptDevice(deviceCacheMap.get(connectId), connectId);
|
|
1161
1347
|
requestQueue.rejectRequest(
|
|
1162
1348
|
requestId,
|
|
1163
1349
|
ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled)
|
|
1164
1350
|
);
|
|
1165
1351
|
}
|
|
1166
1352
|
}
|
|
1167
|
-
|
|
1353
|
+
interruptDevice(deviceCacheMap.get(connectId), connectId);
|
|
1354
|
+
pollingManager.stop(connectId);
|
|
1168
1355
|
} catch (e) {
|
|
1169
1356
|
Log.error('Cancel API Error: ', e);
|
|
1170
1357
|
}
|
|
@@ -1172,23 +1359,33 @@ export const cancel = (context: CoreContext, connectId?: string) => {
|
|
|
1172
1359
|
const env = DataManager.getSettings('env');
|
|
1173
1360
|
if (DataManager.isBleConnect(env)) {
|
|
1174
1361
|
Log.debug('Cancel Api all _deviceList: ');
|
|
1362
|
+
// Keep method abort signals observable until every active task is rejected.
|
|
1363
|
+
requestQueue.abortAllRequests();
|
|
1175
1364
|
const canceledDevices: Device[] = [];
|
|
1365
|
+
const interruptDevice = (device?: Device) => {
|
|
1366
|
+
if (!device || canceledDevices.includes(device)) {
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
device.interruptionFromUser().catch(() => undefined);
|
|
1370
|
+
canceledDevices.push(device);
|
|
1371
|
+
};
|
|
1176
1372
|
for (const requestId of requestQueue.getRequestTasksId()) {
|
|
1177
1373
|
const task = requestQueue.getTask(requestId);
|
|
1178
1374
|
Log.debug('Cancel Api connect task: ', task);
|
|
1179
|
-
if (task
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1375
|
+
if (task) {
|
|
1376
|
+
interruptDevice(task.method?.device);
|
|
1377
|
+
if (task.method.connectId) {
|
|
1378
|
+
interruptDevice(deviceCacheMap.get(task.method.connectId));
|
|
1379
|
+
pollingManager.stop(task.method.connectId);
|
|
1184
1380
|
}
|
|
1185
|
-
|
|
1186
1381
|
requestQueue.rejectRequest(
|
|
1187
1382
|
requestId,
|
|
1188
1383
|
ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled)
|
|
1189
1384
|
);
|
|
1190
1385
|
}
|
|
1191
1386
|
}
|
|
1387
|
+
deviceCacheMap.forEach(interruptDevice);
|
|
1388
|
+
pollingManager.stopAll();
|
|
1192
1389
|
} else {
|
|
1193
1390
|
_deviceList?.allDevices().forEach(device => {
|
|
1194
1391
|
Log.debug('device: ', device, ' device.hasDeviceAcquire: ', device.hasDeviceAcquire());
|
|
@@ -1238,6 +1435,10 @@ const checkPassphraseEnableState = (method: BaseMethod, features?: Features) =>
|
|
|
1238
1435
|
const shouldCheckPassphraseState = (method: BaseMethod, device: Device) => {
|
|
1239
1436
|
if (!method.useDevicePassphraseState) return false;
|
|
1240
1437
|
|
|
1438
|
+
if (device.isProtocolV2() && method.payload?.useEmptyPassphrase === true) {
|
|
1439
|
+
return true;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1241
1442
|
return device.hasUsePassphrase();
|
|
1242
1443
|
};
|
|
1243
1444
|
|
|
@@ -1248,7 +1449,6 @@ const cleanup = () => {
|
|
|
1248
1449
|
pendingUiPromises,
|
|
1249
1450
|
ERRORS.TypedError(HardwareErrorCode.ActionCancelled, 'UI request was cancelled')
|
|
1250
1451
|
);
|
|
1251
|
-
Log.debug('Cleanup...');
|
|
1252
1452
|
};
|
|
1253
1453
|
|
|
1254
1454
|
const removeDeviceListener = (device: Device) => {
|
|
@@ -1510,7 +1710,7 @@ export default class Core extends EventEmitter {
|
|
|
1510
1710
|
private disposePromise?: Promise<void>;
|
|
1511
1711
|
|
|
1512
1712
|
// background task
|
|
1513
|
-
private
|
|
1713
|
+
private prePendingCallPromises = new Map<string, Promise<void>>();
|
|
1514
1714
|
|
|
1515
1715
|
private methodSynchronize = getSynchronize();
|
|
1516
1716
|
|
|
@@ -1527,9 +1727,18 @@ export default class Core extends EventEmitter {
|
|
|
1527
1727
|
tracingContext: this.tracingContext,
|
|
1528
1728
|
requestQueue: this.requestQueue,
|
|
1529
1729
|
methodSynchronize: this.methodSynchronize,
|
|
1530
|
-
getPrePendingCallPromise: () => this.
|
|
1531
|
-
setPrePendingCallPromise: (
|
|
1532
|
-
|
|
1730
|
+
getPrePendingCallPromise: (connectId: string) => this.prePendingCallPromises.get(connectId),
|
|
1731
|
+
setPrePendingCallPromise: (connectId: string, promise?: Promise<void>) => {
|
|
1732
|
+
if (!promise) {
|
|
1733
|
+
this.prePendingCallPromises.delete(connectId);
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
this.prePendingCallPromises.set(connectId, promise);
|
|
1737
|
+
},
|
|
1738
|
+
removePrePendingCallPromise: (connectId: string, promise: Promise<void>) => {
|
|
1739
|
+
if (this.prePendingCallPromises.get(connectId) === promise) {
|
|
1740
|
+
this.prePendingCallPromises.delete(connectId);
|
|
1741
|
+
}
|
|
1533
1742
|
},
|
|
1534
1743
|
// callback 任务管理
|
|
1535
1744
|
registerCallbackTask: (connectId: string, callbackPromise: Deferred<any>) => {
|
|
@@ -1644,7 +1853,7 @@ export default class Core extends EventEmitter {
|
|
|
1644
1853
|
preWarmInflight.clear();
|
|
1645
1854
|
preWarmDoneAt.clear();
|
|
1646
1855
|
preConnectCache = { passphraseState: undefined };
|
|
1647
|
-
this.
|
|
1856
|
+
this.prePendingCallPromises.clear();
|
|
1648
1857
|
this.removeAllListeners();
|
|
1649
1858
|
cleanupSdkInstance(this.sdkInstanceId);
|
|
1650
1859
|
if (_core === this) _core = undefined;
|