@onekeyfe/hd-shared 1.2.0-alpha.18 → 1.2.0-alpha.180
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/dist/HardwareError.d.ts +4 -2
- package/dist/HardwareError.d.ts.map +1 -1
- package/dist/constants.d.ts +34 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/deviceType.d.ts +2 -1
- package/dist/deviceType.d.ts.map +1 -1
- package/dist/index.d.ts +325 -4
- package/dist/index.js +175 -4
- package/jest.config.js +5 -0
- package/package.json +3 -2
- package/src/HardwareError.test.ts +36 -0
- package/src/HardwareError.ts +16 -4
- package/src/constants.test.ts +194 -1
- package/src/constants.ts +242 -5
- package/src/deviceType.ts +1 -0
package/src/constants.ts
CHANGED
|
@@ -10,13 +10,16 @@ export type HardwareConnectProtocol =
|
|
|
10
10
|
|
|
11
11
|
export const ONEKEY_WEBUSB_FILTER = [
|
|
12
12
|
{ vendorId: 0x1209, productId: 0x53c0 }, // Classic Boot、Classic1s Boot、Mini Boot
|
|
13
|
-
{ vendorId: 0x1209, productId: 0x53c1 }, // Classic
|
|
14
|
-
{ vendorId: 0x1209, productId: 0x4f4a }, // Pro
|
|
15
|
-
{ vendorId: 0x1209, productId: 0x4f4b }, // Pro
|
|
16
|
-
{ vendorId: 0x1209, productId: 0x4f4c }, //
|
|
13
|
+
{ vendorId: 0x1209, productId: 0x53c1 }, // Classic/Classic1s/Mini/Pro/Touch firmware and legacy Pro2; keep for existing devices
|
|
14
|
+
{ vendorId: 0x1209, productId: 0x4f4a }, // Pro bootloader, Touch bootloader, Pro2
|
|
15
|
+
{ vendorId: 0x1209, productId: 0x4f4b }, // Pro/Touch firmware (Trezor not implemented), Pro2
|
|
16
|
+
{ vendorId: 0x1209, productId: 0x4f4c }, // Pro2 / Neo current firmware, all modes
|
|
17
17
|
// { vendorId: 0x1209, productId: 0x4f50 }, // Touch Board
|
|
18
18
|
];
|
|
19
19
|
|
|
20
|
+
/** USB IDs whose first probe should be Protocol V2. Shared Pro/Touch PIDs stay unhinted. */
|
|
21
|
+
export const ONEKEY_PROTOCOL_V2_USB_IDS = [{ vendorId: 0x1209, productId: 0x4f4c }] as const;
|
|
22
|
+
|
|
20
23
|
type WebUsbIdentityDescriptor = {
|
|
21
24
|
vendorId?: number;
|
|
22
25
|
productId?: number;
|
|
@@ -86,14 +89,33 @@ export enum EOneKeyBleMessageKeys {
|
|
|
86
89
|
NOBLE_BLE_STOP_SCAN = '$onekey-noble-ble-stop-scan',
|
|
87
90
|
NOBLE_BLE_GET_DEVICE = '$onekey-noble-ble-get-device',
|
|
88
91
|
NOBLE_BLE_CONNECT = '$onekey-noble-ble-connect',
|
|
92
|
+
// Logical end-of-operation from the renderer (keep-alive): the physical link
|
|
93
|
+
// stays up but the main process starts its idle-disconnect countdown.
|
|
94
|
+
NOBLE_BLE_RELEASE = '$onekey-noble-ble-release',
|
|
89
95
|
NOBLE_BLE_DISCONNECT = '$onekey-noble-ble-disconnect',
|
|
90
96
|
NOBLE_BLE_WRITE = '$onekey-noble-ble-write',
|
|
91
97
|
NOBLE_BLE_SUBSCRIBE = '$onekey-noble-ble-subscribe',
|
|
92
98
|
NOBLE_BLE_UNSUBSCRIBE = '$onekey-noble-ble-unsubscribe',
|
|
93
99
|
NOBLE_BLE_NOTIFICATION = '$onekey-noble-ble-notification',
|
|
100
|
+
NOBLE_BLE_MTU_CHANGED = '$onekey-noble-ble-mtu-changed',
|
|
94
101
|
NOBLE_BLE_CANCEL_PAIRING = '$onekey-noble-ble-cancel-pairing',
|
|
95
102
|
}
|
|
96
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Why a BLE link went down, carried on BLE_DEVICE_DISCONNECTED.
|
|
106
|
+
*
|
|
107
|
+
* The main process frees an idle link on its own keep-alive timer, which is an
|
|
108
|
+
* internal optimisation the device knows nothing about — consumers must not
|
|
109
|
+
* treat it as "the device is gone". Only DeviceDisconnected means the
|
|
110
|
+
* peripheral actually dropped.
|
|
111
|
+
*/
|
|
112
|
+
export enum EBleDisconnectReason {
|
|
113
|
+
/** Unsolicited peripheral drop: powered off, out of range, cable/BLE lost. */
|
|
114
|
+
DeviceDisconnected = 'device-disconnected',
|
|
115
|
+
/** Main-process keep-alive timer released an idle link; device still present. */
|
|
116
|
+
IdleKeepAlive = 'idle-keep-alive',
|
|
117
|
+
}
|
|
118
|
+
|
|
97
119
|
export const ONEKEY_SERVICE_UUID = '00000001-0000-1000-8000-00805f9b34fb';
|
|
98
120
|
export const ONEKEY_WRITE_CHARACTERISTIC_UUID = '00000002-0000-1000-8000-00805f9b34fb';
|
|
99
121
|
export const ONEKEY_NOTIFY_CHARACTERISTIC_UUID = '00000003-0000-1000-8000-00805f9b34fb';
|
|
@@ -148,9 +170,224 @@ export const isOnekeyDevice = (name: string | null, id?: string): boolean => {
|
|
|
148
170
|
if (
|
|
149
171
|
normalizedName.startsWith('touch ') ||
|
|
150
172
|
normalizedName.startsWith('pro ') ||
|
|
151
|
-
normalizedName.startsWith('pro2
|
|
173
|
+
normalizedName.startsWith('pro2') ||
|
|
174
|
+
normalizedName.startsWith('neo')
|
|
152
175
|
) {
|
|
153
176
|
return true;
|
|
154
177
|
}
|
|
178
|
+
const compactName = compactBleName(normalizedName);
|
|
179
|
+
if (PRO2_COMPACT_NAME_PATTERN.test(compactName) || NEO_COMPACT_NAME_PATTERN.test(compactName)) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
155
182
|
return isOneKeyShortName(normalizedName);
|
|
156
183
|
};
|
|
184
|
+
|
|
185
|
+
export const inferProtocolHintFromUsbId = (vendorId?: number | null, productId?: number | null) =>
|
|
186
|
+
ONEKEY_PROTOCOL_V2_USB_IDS.some(id => id.vendorId === vendorId && id.productId === productId)
|
|
187
|
+
? ('V2' as const)
|
|
188
|
+
: undefined;
|
|
189
|
+
|
|
190
|
+
type UsbDevicePathInput = {
|
|
191
|
+
serialNumber?: string | null;
|
|
192
|
+
vendorId?: number;
|
|
193
|
+
productId?: number;
|
|
194
|
+
productName?: string | null;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const resolveOneKeyUsbDevicePath = (device: UsbDevicePathInput): string | undefined => {
|
|
198
|
+
const serial = device.serialNumber?.trim();
|
|
199
|
+
if (serial) return serial;
|
|
200
|
+
if (device.vendorId == null && device.productId == null && !device.productName) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const vendorId = (device.vendorId ?? 0).toString(16).padStart(4, '0');
|
|
205
|
+
const productId = (device.productId ?? 0).toString(16).padStart(4, '0');
|
|
206
|
+
const product = (device.productName ?? 'onekey')
|
|
207
|
+
.trim()
|
|
208
|
+
.toLowerCase()
|
|
209
|
+
.replace(/\s+/g, '-')
|
|
210
|
+
.replace(/[^a-z0-9-]/g, '');
|
|
211
|
+
return `usb-${vendorId}-${productId}-${product || 'onekey'}`;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
type BluetoothDeviceIdentity = {
|
|
215
|
+
id?: string;
|
|
216
|
+
name?: string | null;
|
|
217
|
+
localName?: string | null;
|
|
218
|
+
serviceUuids?: Array<string | null | undefined> | null;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const BLUETOOTH_BASE_UUID_SUFFIX = '00001000800000805f9b34fb';
|
|
222
|
+
|
|
223
|
+
export const normalizeBleUuid = (uuid?: string | null) =>
|
|
224
|
+
(uuid ?? '').replace(/-/g, '').toLowerCase();
|
|
225
|
+
|
|
226
|
+
export const createKnownBleUuidAliases = (uuid: string): ReadonlySet<string> => {
|
|
227
|
+
const normalized = normalizeBleUuid(uuid);
|
|
228
|
+
const aliases = new Set([normalized]);
|
|
229
|
+
|
|
230
|
+
if (normalized.length !== 32 || !normalized.endsWith(BLUETOOTH_BASE_UUID_SUFFIX)) {
|
|
231
|
+
return aliases;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const assignedNumber = normalized.slice(0, 8);
|
|
235
|
+
aliases.add(assignedNumber);
|
|
236
|
+
if (assignedNumber.startsWith('0000')) {
|
|
237
|
+
aliases.add(assignedNumber.slice(4));
|
|
238
|
+
}
|
|
239
|
+
return aliases;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const matchesKnownBleUuid = (
|
|
243
|
+
actualUuid: string | null | undefined,
|
|
244
|
+
aliases: ReadonlySet<string>
|
|
245
|
+
) => aliases.has(normalizeBleUuid(actualUuid));
|
|
246
|
+
|
|
247
|
+
const ONEKEY_COMMUNICATION_SERVICE_ALIASES = createKnownBleUuidAliases(ONEKEY_SERVICE_UUID);
|
|
248
|
+
const FIDO_SERVICE_ALIASES = createKnownBleUuidAliases('0000fffd-0000-1000-8000-00805f9b34fb');
|
|
249
|
+
const PRO2_COMPACT_NAME_PATTERN = /^(?:onekey)?pro2[a-f0-9]{4}$/i;
|
|
250
|
+
const NEO_COMPACT_NAME_PATTERN = /^(?:onekey)?neo[a-f0-9]{4}$/i;
|
|
251
|
+
const FIND_MY_COMPACT_SUFFIXES = [
|
|
252
|
+
'findemy',
|
|
253
|
+
'findem',
|
|
254
|
+
'finde',
|
|
255
|
+
'findmy',
|
|
256
|
+
'findm',
|
|
257
|
+
'find',
|
|
258
|
+
'fin',
|
|
259
|
+
] as const;
|
|
260
|
+
const COMPLETE_FIND_MY_COMPACT_SUFFIXES = ['findemy', 'findmy'] as const;
|
|
261
|
+
|
|
262
|
+
const compactBleName = (value: string) =>
|
|
263
|
+
Array.from(value)
|
|
264
|
+
.filter(character => character !== '-' && character.trim() !== '')
|
|
265
|
+
.join('')
|
|
266
|
+
.toLowerCase();
|
|
267
|
+
|
|
268
|
+
const getFindMyCompactSuffix = (compactName: string) =>
|
|
269
|
+
FIND_MY_COMPACT_SUFFIXES.find(candidate => compactName.endsWith(candidate));
|
|
270
|
+
|
|
271
|
+
const getPro2FindMyBaseLength = (value: string) => {
|
|
272
|
+
const compactName = compactBleName(value);
|
|
273
|
+
const suffix = getFindMyCompactSuffix(compactName);
|
|
274
|
+
if (!suffix) return undefined;
|
|
275
|
+
|
|
276
|
+
const compactBaseName = compactName.slice(0, -suffix.length);
|
|
277
|
+
return PRO2_COMPACT_NAME_PATTERN.test(compactBaseName) ? compactBaseName.length : undefined;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
export const isPro2FindMyAdvertisementName = (value?: string | null) => {
|
|
281
|
+
if (!value) return false;
|
|
282
|
+
if (getPro2FindMyBaseLength(value) !== undefined) return true;
|
|
283
|
+
|
|
284
|
+
const compactName = compactBleName(value);
|
|
285
|
+
const suffix = COMPLETE_FIND_MY_COMPACT_SUFFIXES.find(candidate =>
|
|
286
|
+
compactName.endsWith(candidate)
|
|
287
|
+
);
|
|
288
|
+
if (!suffix) return false;
|
|
289
|
+
|
|
290
|
+
return compactName.slice(0, -suffix.length).includes('pro2');
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export const normalizePro2FindMyAdvertisementName = (value: string) => {
|
|
294
|
+
const baseLength = getPro2FindMyBaseLength(value);
|
|
295
|
+
if (baseLength === undefined) return value;
|
|
296
|
+
|
|
297
|
+
let compactLength = 0;
|
|
298
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
299
|
+
const character = value[index];
|
|
300
|
+
if (character !== '-' && character.trim() !== '') {
|
|
301
|
+
compactLength += 1;
|
|
302
|
+
if (compactLength === baseLength) {
|
|
303
|
+
return value.slice(0, index + 1).trimEnd();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return value;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Current Pro2 advertisements use "Pro 2 XXXX". Older firmware used "Pro2 XXXX".
|
|
312
|
+
* Discovery and DeviceInfo both go through this helper so the public BLE name
|
|
313
|
+
* stays on the spaced form without changing OneKey Pro / Neo names.
|
|
314
|
+
*
|
|
315
|
+
* Match the compact Pro2 form first. A leading "Pro 2" regex would also eat the
|
|
316
|
+
* first digit of a OneKey Pro suffix such as "Pro 22D8" or "Pro 2D8F".
|
|
317
|
+
*/
|
|
318
|
+
export const canonicalizePro2BleAdvertisementName = (value: string) => {
|
|
319
|
+
const withoutFindMy = normalizePro2FindMyAdvertisementName(value);
|
|
320
|
+
const compact = compactBleName(withoutFindMy);
|
|
321
|
+
if (!PRO2_COMPACT_NAME_PATTERN.test(compact)) return withoutFindMy;
|
|
322
|
+
|
|
323
|
+
const match = withoutFindMy.match(/^(onekey\s*)?pro\s*2\s*/i);
|
|
324
|
+
if (!match) return withoutFindMy;
|
|
325
|
+
|
|
326
|
+
const rest = withoutFindMy.slice(match[0].length);
|
|
327
|
+
const prefix = match[1] ? 'OneKey Pro 2' : 'Pro 2';
|
|
328
|
+
return rest ? `${prefix} ${rest}` : prefix;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
export const isSameOnekeyBleName = (left?: string | null, right?: string | null) => {
|
|
332
|
+
if (!left || !right) return false;
|
|
333
|
+
if (left === right) return true;
|
|
334
|
+
return (
|
|
335
|
+
compactBleName(canonicalizePro2BleAdvertisementName(left)) ===
|
|
336
|
+
compactBleName(canonicalizePro2BleAdvertisementName(right))
|
|
337
|
+
);
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
export const hasOnekeyCommunicationService = (
|
|
341
|
+
serviceUuids: Array<string | null | undefined> | null | undefined
|
|
342
|
+
) =>
|
|
343
|
+
(serviceUuids ?? []).some(uuid =>
|
|
344
|
+
matchesKnownBleUuid(uuid, ONEKEY_COMMUNICATION_SERVICE_ALIASES)
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Protocol V2 family (Pro2 / Neo) by advertised BLE name. Callers use it to
|
|
349
|
+
* pick a connection strategy: this family also advertises under Find My names
|
|
350
|
+
* that carry no OneKey service UUID, so a scan cannot always see it.
|
|
351
|
+
*/
|
|
352
|
+
export const isPro2FamilyBleName = (value?: string | null): boolean => {
|
|
353
|
+
if (!value) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
const normalized = value.trim().toLowerCase();
|
|
357
|
+
if (!normalized) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
if (normalized.startsWith('pro2') || normalized.startsWith('neo')) {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
const compact = compactBleName(normalized);
|
|
364
|
+
return (
|
|
365
|
+
PRO2_COMPACT_NAME_PATTERN.test(compact) ||
|
|
366
|
+
NEO_COMPACT_NAME_PATTERN.test(compact) ||
|
|
367
|
+
isPro2FindMyAdvertisementName(value)
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
export const isOnekeyBluetoothDevice = ({
|
|
372
|
+
id,
|
|
373
|
+
name,
|
|
374
|
+
localName,
|
|
375
|
+
serviceUuids,
|
|
376
|
+
}: BluetoothDeviceIdentity): boolean => {
|
|
377
|
+
const advertisedServiceUuids = serviceUuids ?? [];
|
|
378
|
+
if (hasOnekeyCommunicationService(advertisedServiceUuids)) {
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Android can return a connected Find My peripheral without its advertised
|
|
383
|
+
// services. Do not let the Pro2-looking name fall through to name discovery.
|
|
384
|
+
if (isPro2FindMyAdvertisementName(name) || isPro2FindMyAdvertisementName(localName)) {
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (advertisedServiceUuids.some(uuid => matchesKnownBleUuid(uuid, FIDO_SERVICE_ALIASES))) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return isOnekeyDevice(name ?? null, id) || isOnekeyDevice(localName ?? null, id);
|
|
393
|
+
};
|