@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/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 Firmware、Classic1s Firmware、Mini Firmware、Pro Firmware、Touch Firmware、Pro2(旧固件,勿删:存量设备仍以此 PID 枚举)
14
- { vendorId: 0x1209, productId: 0x4f4a }, // Pro Boot、Touch Boot、Pro2
15
- { vendorId: 0x1209, productId: 0x4f4b }, // Pro Firmware、Touch Firmware(Not implemented Trezor)、Pro2
16
- { vendorId: 0x1209, productId: 0x4f4c }, // Pro Board、Pro2(新固件 PID)
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
+ };
package/src/deviceType.ts CHANGED
@@ -7,4 +7,5 @@ export enum EDeviceType {
7
7
  Touch = 'touch',
8
8
  Pro = 'pro',
9
9
  Pro2 = 'pro2',
10
+ Neo = 'neo',
10
11
  }