@onekeyfe/hd-core 1.2.0-alpha.162 → 1.2.0-alpha.164
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__/device-state-events.test.ts +2 -2
- package/__tests__/device-state-mapper.test.ts +2 -11
- package/__tests__/device-utils.test.ts +0 -6
- package/__tests__/firmware-memory-host.test.ts +126 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +3 -13
- package/__tests__/protocol-v2-resources.test.ts +2 -35
- package/__tests__/protocol-v2.test.ts +42 -89
- package/__tests__/search-devices.test.ts +3 -4
- package/dist/api/FirmwareUpdateV4.d.ts +1 -3
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareMemoryHost.d.ts +22 -0
- package/dist/api/firmware/FirmwareMemoryHost.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +15 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/index.d.ts +22 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +298 -102
- package/dist/protocols/protocol-v2/resources.d.ts +0 -1
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +179 -75
- package/src/api/SearchDevices.ts +2 -2
- package/src/api/firmware/FirmwareMemoryHost.ts +143 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +41 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +6 -10
- package/src/core/index.ts +97 -6
- package/src/device/Device.ts +1 -3
- package/src/device/DeviceStateMapper.ts +2 -2
- package/src/deviceProfile/buildDeviceFeatures.ts +2 -7
- package/src/index.ts +6 -0
- package/src/protocols/protocol-v2/resources.ts +12 -16
- package/src/types/api/firmwareUpdate.ts +1 -1
- package/src/utils/deviceFeaturesCompat.ts +4 -9
- package/src/utils/deviceInfoUtils.ts +1 -3
package/src/core/index.ts
CHANGED
|
@@ -415,6 +415,10 @@ const onCallDevice = async (
|
|
|
415
415
|
if (method.payload?.onlyConnectBleDevice) {
|
|
416
416
|
preWarmCallbackTask?.resolve();
|
|
417
417
|
Log.debug('Call API - only connect ble device: ', device?.mainId);
|
|
418
|
+
// This early return bypasses the normal-path releaseTask 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
|
+
requestQueue.releaseTask(method.responseID);
|
|
418
422
|
return createResponseMessage(method.responseID, true, null);
|
|
419
423
|
}
|
|
420
424
|
|
|
@@ -954,7 +958,60 @@ export function isMissingDetectedProtocolV2Error(method: BaseMethod, error: unkn
|
|
|
954
958
|
* If the Bluetooth connection times out, retry up to 6 times
|
|
955
959
|
* @param retryCount - Current retry count (default 0)
|
|
956
960
|
*/
|
|
957
|
-
|
|
961
|
+
// device.acquire awaits a transport reply with no deadline of its own; a
|
|
962
|
+
// transport that never settles (field case: Electron main lost an IPC reply,
|
|
963
|
+
// "reply was never sent" after 5 minutes) hangs the call forever and cancel()
|
|
964
|
+
// only takes effect at poll checkpoints. Race acquire against a deadline and
|
|
965
|
+
// the caller's abort signal so the hang is bounded and cancel is immediate.
|
|
966
|
+
const BLE_ACQUIRE_DEADLINE_MS = 60 * 1000;
|
|
967
|
+
|
|
968
|
+
function raceBleAcquire<T>(acquirePromise: Promise<T>, abortSignal?: AbortSignal): Promise<T> {
|
|
969
|
+
return new Promise<T>((resolve, reject) => {
|
|
970
|
+
let settled = false;
|
|
971
|
+
const settle = (fn: () => void) => {
|
|
972
|
+
if (settled) return;
|
|
973
|
+
settled = true;
|
|
974
|
+
clearTimeout(deadline);
|
|
975
|
+
abortSignal?.removeEventListener('abort', onAbort);
|
|
976
|
+
fn();
|
|
977
|
+
};
|
|
978
|
+
const onAbort = () =>
|
|
979
|
+
settle(() => reject(ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled)));
|
|
980
|
+
const deadline = setTimeout(
|
|
981
|
+
() =>
|
|
982
|
+
settle(() =>
|
|
983
|
+
reject(
|
|
984
|
+
ERRORS.TypedError(
|
|
985
|
+
HardwareErrorCode.BleTimeoutError,
|
|
986
|
+
`BLE acquire exceeded ${BLE_ACQUIRE_DEADLINE_MS}ms deadline`
|
|
987
|
+
)
|
|
988
|
+
)
|
|
989
|
+
),
|
|
990
|
+
BLE_ACQUIRE_DEADLINE_MS
|
|
991
|
+
);
|
|
992
|
+
// Attach before any early return so a late settlement of acquirePromise
|
|
993
|
+
// is always consumed — an abort or deadline must never leave the acquire
|
|
994
|
+
// rejection unhandled.
|
|
995
|
+
acquirePromise.then(
|
|
996
|
+
value => settle(() => resolve(value)),
|
|
997
|
+
error => settle(() => reject(error))
|
|
998
|
+
);
|
|
999
|
+
if (abortSignal) {
|
|
1000
|
+
if (abortSignal.aborted) {
|
|
1001
|
+
onAbort();
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
abortSignal.addEventListener('abort', onAbort);
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
async function connectDeviceForBle(
|
|
1010
|
+
method: BaseMethod,
|
|
1011
|
+
device: Device,
|
|
1012
|
+
abortSignal?: AbortSignal,
|
|
1013
|
+
retryCount = 0
|
|
1014
|
+
) {
|
|
958
1015
|
try {
|
|
959
1016
|
if (device.wasInterruptedByUser()) {
|
|
960
1017
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
@@ -968,9 +1025,43 @@ async function connectDeviceForBle(method: BaseMethod, device: Device, retryCoun
|
|
|
968
1025
|
!device.commands ||
|
|
969
1026
|
device.commands.disposed;
|
|
970
1027
|
if (shouldAcquire) {
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1028
|
+
// The deadline/abort guards are scoped to the desktop electron
|
|
1029
|
+
// transport: its IPC acquire is the only path with a proven
|
|
1030
|
+
// never-settling failure mode, while react-native/lowlevel acquire may
|
|
1031
|
+
// legitimately block on a user-driven system bonding prompt for longer
|
|
1032
|
+
// than any sane deadline. Other envs keep the plain acquire unchanged.
|
|
1033
|
+
const useAcquireGuards = DataManager.getSettings('env') === 'desktop-web-ble';
|
|
1034
|
+
// A cancel landing during the retry backoff must not start a new acquire.
|
|
1035
|
+
if (useAcquireGuards && abortSignal?.aborted) {
|
|
1036
|
+
throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
1037
|
+
}
|
|
1038
|
+
if (!useAcquireGuards) {
|
|
1039
|
+
await device.acquire(method.payload.connectProtocol, {
|
|
1040
|
+
forceProtocolDetection: method.payload.forceProtocolDetection,
|
|
1041
|
+
});
|
|
1042
|
+
} else {
|
|
1043
|
+
try {
|
|
1044
|
+
await raceBleAcquire(
|
|
1045
|
+
device.acquire(method.payload.connectProtocol, {
|
|
1046
|
+
forceProtocolDetection: method.payload.forceProtocolDetection,
|
|
1047
|
+
}),
|
|
1048
|
+
abortSignal
|
|
1049
|
+
);
|
|
1050
|
+
} catch (err) {
|
|
1051
|
+
// A deadline hit means the transport is wedged mid-acquire; drop the
|
|
1052
|
+
// link before the retry so it cold-connects instead of stacking a
|
|
1053
|
+
// second connect onto the half-open one.
|
|
1054
|
+
if (
|
|
1055
|
+
err.errorCode === HardwareErrorCode.BleTimeoutError &&
|
|
1056
|
+
device.mainId &&
|
|
1057
|
+
device.deviceConnector
|
|
1058
|
+
) {
|
|
1059
|
+
await device.deviceConnector.disconnect(device.mainId).catch(() => undefined);
|
|
1060
|
+
device.markTransportDisconnected();
|
|
1061
|
+
}
|
|
1062
|
+
throw err;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
974
1065
|
}
|
|
975
1066
|
if (method.payload?.onlyConnectBleDevice) {
|
|
976
1067
|
if (shouldAcquire) {
|
|
@@ -1010,7 +1101,7 @@ async function connectDeviceForBle(method: BaseMethod, device: Device, retryCoun
|
|
|
1010
1101
|
const nextRetry = retryCount + 1;
|
|
1011
1102
|
Log.debug(`Bluetooth connection will retry, retry count: ${nextRetry}`);
|
|
1012
1103
|
await wait(3000);
|
|
1013
|
-
await connectDeviceForBle(method, device, nextRetry);
|
|
1104
|
+
await connectDeviceForBle(method, device, abortSignal, nextRetry);
|
|
1014
1105
|
} else {
|
|
1015
1106
|
throw err;
|
|
1016
1107
|
}
|
|
@@ -1120,7 +1211,7 @@ const ensureConnected = async (
|
|
|
1120
1211
|
if (tryCount === 1) {
|
|
1121
1212
|
device.beginConnectionAttempt();
|
|
1122
1213
|
}
|
|
1123
|
-
await connectDeviceForBle(method, device);
|
|
1214
|
+
await connectDeviceForBle(method, device, abortSignal);
|
|
1124
1215
|
}
|
|
1125
1216
|
resolve(device);
|
|
1126
1217
|
return;
|
package/src/device/Device.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
ERROR_CODES_REQUIRE_RELEASE,
|
|
10
10
|
HardwareError,
|
|
11
11
|
HardwareErrorCode,
|
|
12
|
-
canonicalizePro2BleAdvertisementName,
|
|
13
12
|
createDeferred,
|
|
14
13
|
createDeviceNotSupportMethodError,
|
|
15
14
|
} from '@onekeyfe/hd-shared';
|
|
@@ -673,8 +672,7 @@ export class Device extends EventEmitter {
|
|
|
673
672
|
}
|
|
674
673
|
|
|
675
674
|
getCurrentBleName() {
|
|
676
|
-
|
|
677
|
-
return bleName ? canonicalizePro2BleAdvertisementName(bleName) : null;
|
|
675
|
+
return this.state?.identity.bleName ?? null;
|
|
678
676
|
}
|
|
679
677
|
|
|
680
678
|
getCurrentLabel() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EFirmwareType,
|
|
1
|
+
import { EFirmwareType, normalizePro2FindMyAdvertisementName } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
3
|
import { buildProtocolV1FeaturesPayload } from '../deviceProfile/buildDeviceFeatures';
|
|
4
4
|
import { resolveProtocolV2DeviceIdentity } from '../deviceProfile/protocolV2DeviceIdentity';
|
|
@@ -248,7 +248,7 @@ export const mapProtocolV2DeviceInfoToState = (
|
|
|
248
248
|
vendor: 'onekey.so',
|
|
249
249
|
serialNo: info.hw?.serial_no,
|
|
250
250
|
bleName: info.coprocessor?.bt_adv_name
|
|
251
|
-
?
|
|
251
|
+
? normalizePro2FindMyAdvertisementName(info.coprocessor.bt_adv_name)
|
|
252
252
|
: undefined,
|
|
253
253
|
deviceId: loader ? null : undefined,
|
|
254
254
|
}),
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
EDeviceType,
|
|
3
|
-
EFirmwareType,
|
|
4
|
-
canonicalizePro2BleAdvertisementName,
|
|
5
|
-
} from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
|
|
6
2
|
|
|
7
3
|
import {
|
|
8
4
|
resolveDeviceBleFirmwareVersion,
|
|
@@ -249,8 +245,7 @@ export const buildProtocolV2FeaturesPayload = ({
|
|
|
249
245
|
const deviceId = status?.device_id ?? cached?.deviceId ?? null;
|
|
250
246
|
const serialNo = firstValue(incomingSerialNo, cached?.serialNo) ?? '';
|
|
251
247
|
const label = cached?.label ?? null;
|
|
252
|
-
const
|
|
253
|
-
const bleName = rawBleName ? canonicalizePro2BleAdvertisementName(rawBleName) : rawBleName;
|
|
248
|
+
const bleName = firstValue(info?.coprocessor?.bt_adv_name, cached?.bleName);
|
|
254
249
|
const initialized = firstValue(status?.init_states, cached?.initialized) ?? null;
|
|
255
250
|
// passphrase_enabled from a locked Pro2 is not authoritative. Only DeviceStatus
|
|
256
251
|
// after PIN unlock can determine the final passphrase setting.
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,12 @@ export { executeCallback, cleanupCallback };
|
|
|
21
21
|
export { preloadSessionCache } from './device/Device';
|
|
22
22
|
export { projectFeatures as projectDeviceStateFeatures } from './device/DeviceStateProjector';
|
|
23
23
|
export { getMethodSupportedProtocols } from './api/utils';
|
|
24
|
+
export { prepareFirmwareUpdateV4MemoryHost } from './api/firmware/FirmwareMemoryHost';
|
|
25
|
+
export type {
|
|
26
|
+
FirmwareMemoryArtifact,
|
|
27
|
+
FirmwareMemoryArtifactEntry,
|
|
28
|
+
FirmwareUpdateV4MemoryHost,
|
|
29
|
+
} from './api/firmware/FirmwareMemoryHost';
|
|
24
30
|
export {
|
|
25
31
|
getFirmwareUpdateHostBindingGeneration,
|
|
26
32
|
registerFirmwareUpdateHostBinding,
|
|
@@ -49,22 +49,10 @@ export function parseProtocolV2Resources(value: unknown): IProtocolV2Resources |
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
const normalized = entryName.replace(/\\/g, '/');
|
|
54
|
-
if (!normalized.toLowerCase().endsWith('.okpkg')) {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
const parts = normalized.split('/');
|
|
58
|
-
const fileName = parts[parts.length - 1] ?? '';
|
|
59
|
-
return (
|
|
60
|
-
fileName.length > 0 &&
|
|
61
|
-
!fileName.startsWith('.') &&
|
|
62
|
-
!parts.some(part => part === '__MACOSX' || part === '.' || part === '..' || part === '')
|
|
63
|
-
);
|
|
64
|
-
}
|
|
52
|
+
const PROTOCOL_V2_RESOURCE_DEVICE_ROOTS = ['vol0:/bundles/', 'vol0:/loaders/rom/'] as const;
|
|
65
53
|
|
|
66
|
-
function
|
|
67
|
-
|
|
54
|
+
function isAllowedResourceDevicePath(path: string): boolean {
|
|
55
|
+
if (
|
|
68
56
|
path.includes('\\') ||
|
|
69
57
|
path.includes('//') ||
|
|
70
58
|
[...path].some(char => {
|
|
@@ -72,6 +60,14 @@ function isSafeResourceDevicePath(path: string): boolean {
|
|
|
72
60
|
return code <= 0x1f || code === 0x7f;
|
|
73
61
|
}) ||
|
|
74
62
|
path.split('/').some(part => part === '.' || part === '..')
|
|
63
|
+
) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
if (path === PROTOCOL_V2_BOOT_RESOURCE_PACKAGE_STAGING_PATH) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return (
|
|
70
|
+
path.endsWith('.okpkg') && PROTOCOL_V2_RESOURCE_DEVICE_ROOTS.some(root => path.startsWith(root))
|
|
75
71
|
);
|
|
76
72
|
}
|
|
77
73
|
|
|
@@ -97,7 +93,7 @@ function readResourceDevicePath(bytes: Uint8Array): string {
|
|
|
97
93
|
throw new Error('Invalid Pro2 RESOURCE package device path metadata');
|
|
98
94
|
}
|
|
99
95
|
const path = readAscii(pathBytes, 0, pathBytes.byteLength);
|
|
100
|
-
if (!
|
|
96
|
+
if (!isAllowedResourceDevicePath(path)) {
|
|
101
97
|
throw new Error(`Invalid Pro2 RESOURCE package device path: ${path}`);
|
|
102
98
|
}
|
|
103
99
|
return path;
|
|
@@ -158,7 +158,7 @@ export interface FirmwareUpdateV4Params {
|
|
|
158
158
|
se02Binary?: ArrayBuffer;
|
|
159
159
|
se03Binary?: ArrayBuffer;
|
|
160
160
|
se04Binary?: ArrayBuffer;
|
|
161
|
-
/** Complete Protocol V2 resource ZIP
|
|
161
|
+
/** Complete Protocol V2 resource ZIP for local development; Core converts it to a local PreparedPlan. */
|
|
162
162
|
resourceArchiveBinary?: ArrayBuffer;
|
|
163
163
|
forcedUpdateRes?: boolean;
|
|
164
164
|
artifactReader?: FirmwareArtifactReader;
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
EDeviceType,
|
|
3
|
-
EFirmwareType,
|
|
4
|
-
canonicalizePro2BleAdvertisementName,
|
|
5
|
-
} from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
|
|
6
2
|
import { Enum_Capability } from '@onekeyfe/hd-transport';
|
|
7
3
|
|
|
8
4
|
import type { PROTO } from '../constants';
|
|
@@ -129,10 +125,9 @@ export const resolveDeviceFirmwareType = (features?: DeviceFeaturesInput): EFirm
|
|
|
129
125
|
export const resolveDeviceBleName = (features?: DeviceFeaturesInput): string | null => {
|
|
130
126
|
if (!features) return null;
|
|
131
127
|
const compatible = asCompatibleFeatures(features);
|
|
132
|
-
|
|
133
|
-
firstNonEmptyString(compatible.bleName, compatible.onekey_ble_name, compatible.ble_name) ??
|
|
134
|
-
|
|
135
|
-
return bleName ? canonicalizePro2BleAdvertisementName(bleName) : null;
|
|
128
|
+
return (
|
|
129
|
+
firstNonEmptyString(compatible.bleName, compatible.onekey_ble_name, compatible.ble_name) ?? null
|
|
130
|
+
);
|
|
136
131
|
};
|
|
137
132
|
|
|
138
133
|
export const resolveDeviceFirmwareVersion = (features?: DeviceFeaturesInput): string | null => {
|
|
@@ -31,9 +31,7 @@ export const getDeviceTypeByBleName = (name?: string): IDeviceType => {
|
|
|
31
31
|
if (/^Touch/i.test(name)) return EDeviceType.Touch;
|
|
32
32
|
|
|
33
33
|
const compactName = name.replace(/[\s-]/g, '');
|
|
34
|
-
|
|
35
|
-
// would also match OneKey Pro names such as "Pro 22D8" / "Pro 2D8F".
|
|
36
|
-
if (/\bPro\s*2\b/i.test(name) || /^(?:OneKey)?Pro2[a-f0-9]{4}$/i.test(compactName)) {
|
|
34
|
+
if (/\bPro\s*2\b/i.test(name) || /^Pro2/i.test(name) || /^(?:OneKey)?Pro2/i.test(compactName)) {
|
|
37
35
|
return EDeviceType.Pro2;
|
|
38
36
|
}
|
|
39
37
|
if (/\bNeo\b/i.test(name) || /^Neo/i.test(name) || /^(?:OneKey)?Neo/i.test(compactName)) {
|