@onekeyfe/hwk-desktop-noble-ble 1.2.8-alpha.13 → 1.2.8-alpha.52

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/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { ElectronBleScanOptions, ElectronBleConnectOptions } from '@onekeyfe/hwk-adapter-core';
1
+ import { ElectronBleConnectOptions, ElectronBleScanOptions } from '@onekeyfe/hwk-adapter-core';
2
2
  export { THIRD_PARTY_BLE_CHANNELS, THIRD_PARTY_BLE_DEVICE_TTL_MS, THIRD_PARTY_BLE_POWER_ON_TIMEOUT_MS, THIRD_PARTY_BLE_SCAN_DURATION_MS, THIRD_PARTY_BLE_SCAN_IDLE_STOP_MS, ThirdPartyBleChannel } from './constants.mjs';
3
3
 
4
4
  type BleDebugLogLevel = 'debug' | 'info' | 'warn' | 'error';
@@ -12,6 +12,26 @@ type BleDebugLogger = (entry: BleDebugLogEntry) => void;
12
12
  /** Replace sensitive values at any depth; everything else is forwarded verbatim. */
13
13
  declare function redactBleDebugLogData(data?: Record<string, unknown>): Record<string, unknown> | undefined;
14
14
 
15
+ /**
16
+ * Main-process allowlist entry for one vendor key. The renderer only names the vendor; which
17
+ * peripherals it may see and which GATT profile and framing it may use are decided here, so an
18
+ * IPC caller cannot turn the service into a generic BLE proxy. UUID arguments may arrive in any
19
+ * case, with or without dashes.
20
+ */
21
+ interface ThirdPartyBleVendorProfile {
22
+ /** Whether an advertised service UUID belongs to this vendor. */
23
+ isServiceUuid(uuid: string): boolean;
24
+ /** Whether a local name belongs to this vendor; the handler passes a length-capped name. */
25
+ isName?(name: string): boolean;
26
+ /** Whether this GATT profile and write framing is one the vendor's firmware uses. */
27
+ acceptsConnect(options: ElectronBleConnectOptions): boolean;
28
+ }
29
+ /**
30
+ * Vendors the desktop BLE service serves by default, keyed by the vendor string the connectors
31
+ * send. A host passing `vendors` replaces this map, so spread it to add a vendor.
32
+ */
33
+ declare const DEFAULT_THIRD_PARTY_BLE_VENDORS: Readonly<Record<string, ThirdPartyBleVendorProfile>>;
34
+
15
35
  /** Device info the main process returns to the renderer over IPC. */
16
36
  interface ThirdPartyBleDeviceInfo {
17
37
  /** Stable id (noble peripheral.id) used as connectId. */
@@ -139,6 +159,11 @@ interface NobleBleHandlerOptions {
139
159
  /** Override the overall connect timeout (tests only; defaults to 31s). */
140
160
  connectTimeoutMs?: number;
141
161
  logger?: BleDebugLogger;
162
+ /**
163
+ * Vendors served over IPC, keyed by the `vendor` the renderer sends. Replaces the default map
164
+ * (spread `DEFAULT_THIRD_PARTY_BLE_VENDORS` to add one); unknown vendors are rejected.
165
+ */
166
+ vendors?: Record<string, ThirdPartyBleVendorProfile>;
142
167
  }
143
168
  /** Noble BLE logic decoupled from Electron IPC, so tests can use a fake noble. */
144
169
  declare class NobleBleHandler {
@@ -146,6 +171,7 @@ declare class NobleBleHandler {
146
171
  private readonly _factory;
147
172
  private readonly _connectTimeoutMs;
148
173
  private readonly _logger?;
174
+ private readonly _vendors;
149
175
  private readonly _discovered;
150
176
  private readonly _lastSeen;
151
177
  private readonly _connected;
@@ -167,6 +193,7 @@ declare class NobleBleHandler {
167
193
  private _nativeReleased;
168
194
  private _lastNobleRecoverAt?;
169
195
  constructor(options?: NobleBleHandlerOptions);
196
+ private _resolveVendor;
170
197
  setNotificationListener(handler: (id: string, hexData: string) => void): void;
171
198
  setDisconnectedListener(handler: (id: string) => void): void;
172
199
  init(): Promise<void>;
@@ -274,4 +301,4 @@ interface ThirdPartyBleSupportHandle {
274
301
  */
275
302
  declare function initThirdPartyBleSupport(webContents: WebContentsLike, options?: InitThirdPartyBleSupportOptions): ThirdPartyBleSupportHandle;
276
303
 
277
- export { type BleDebugLogEntry, type BleDebugLogLevel, type BleDebugLogger, type InitThirdPartyBleSupportOptions, type IpcMainLike, NobleBleHandler, type NobleBleHandlerOptions, type ThirdPartyBleApi, type ThirdPartyBleAvailability, type ThirdPartyBleDeviceInfo, type ThirdPartyBleSupportHandle, initThirdPartyBleSupport, redactBleDebugLogData };
304
+ export { type BleDebugLogEntry, type BleDebugLogLevel, type BleDebugLogger, DEFAULT_THIRD_PARTY_BLE_VENDORS, type InitThirdPartyBleSupportOptions, type IpcMainLike, NobleBleHandler, type NobleBleHandlerOptions, type ThirdPartyBleApi, type ThirdPartyBleAvailability, type ThirdPartyBleDeviceInfo, type ThirdPartyBleSupportHandle, type ThirdPartyBleVendorProfile, initThirdPartyBleSupport, redactBleDebugLogData };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ElectronBleScanOptions, ElectronBleConnectOptions } from '@onekeyfe/hwk-adapter-core';
1
+ import { ElectronBleConnectOptions, ElectronBleScanOptions } from '@onekeyfe/hwk-adapter-core';
2
2
  export { THIRD_PARTY_BLE_CHANNELS, THIRD_PARTY_BLE_DEVICE_TTL_MS, THIRD_PARTY_BLE_POWER_ON_TIMEOUT_MS, THIRD_PARTY_BLE_SCAN_DURATION_MS, THIRD_PARTY_BLE_SCAN_IDLE_STOP_MS, ThirdPartyBleChannel } from './constants.js';
3
3
 
4
4
  type BleDebugLogLevel = 'debug' | 'info' | 'warn' | 'error';
@@ -12,6 +12,26 @@ type BleDebugLogger = (entry: BleDebugLogEntry) => void;
12
12
  /** Replace sensitive values at any depth; everything else is forwarded verbatim. */
13
13
  declare function redactBleDebugLogData(data?: Record<string, unknown>): Record<string, unknown> | undefined;
14
14
 
15
+ /**
16
+ * Main-process allowlist entry for one vendor key. The renderer only names the vendor; which
17
+ * peripherals it may see and which GATT profile and framing it may use are decided here, so an
18
+ * IPC caller cannot turn the service into a generic BLE proxy. UUID arguments may arrive in any
19
+ * case, with or without dashes.
20
+ */
21
+ interface ThirdPartyBleVendorProfile {
22
+ /** Whether an advertised service UUID belongs to this vendor. */
23
+ isServiceUuid(uuid: string): boolean;
24
+ /** Whether a local name belongs to this vendor; the handler passes a length-capped name. */
25
+ isName?(name: string): boolean;
26
+ /** Whether this GATT profile and write framing is one the vendor's firmware uses. */
27
+ acceptsConnect(options: ElectronBleConnectOptions): boolean;
28
+ }
29
+ /**
30
+ * Vendors the desktop BLE service serves by default, keyed by the vendor string the connectors
31
+ * send. A host passing `vendors` replaces this map, so spread it to add a vendor.
32
+ */
33
+ declare const DEFAULT_THIRD_PARTY_BLE_VENDORS: Readonly<Record<string, ThirdPartyBleVendorProfile>>;
34
+
15
35
  /** Device info the main process returns to the renderer over IPC. */
16
36
  interface ThirdPartyBleDeviceInfo {
17
37
  /** Stable id (noble peripheral.id) used as connectId. */
@@ -139,6 +159,11 @@ interface NobleBleHandlerOptions {
139
159
  /** Override the overall connect timeout (tests only; defaults to 31s). */
140
160
  connectTimeoutMs?: number;
141
161
  logger?: BleDebugLogger;
162
+ /**
163
+ * Vendors served over IPC, keyed by the `vendor` the renderer sends. Replaces the default map
164
+ * (spread `DEFAULT_THIRD_PARTY_BLE_VENDORS` to add one); unknown vendors are rejected.
165
+ */
166
+ vendors?: Record<string, ThirdPartyBleVendorProfile>;
142
167
  }
143
168
  /** Noble BLE logic decoupled from Electron IPC, so tests can use a fake noble. */
144
169
  declare class NobleBleHandler {
@@ -146,6 +171,7 @@ declare class NobleBleHandler {
146
171
  private readonly _factory;
147
172
  private readonly _connectTimeoutMs;
148
173
  private readonly _logger?;
174
+ private readonly _vendors;
149
175
  private readonly _discovered;
150
176
  private readonly _lastSeen;
151
177
  private readonly _connected;
@@ -167,6 +193,7 @@ declare class NobleBleHandler {
167
193
  private _nativeReleased;
168
194
  private _lastNobleRecoverAt?;
169
195
  constructor(options?: NobleBleHandlerOptions);
196
+ private _resolveVendor;
170
197
  setNotificationListener(handler: (id: string, hexData: string) => void): void;
171
198
  setDisconnectedListener(handler: (id: string) => void): void;
172
199
  init(): Promise<void>;
@@ -274,4 +301,4 @@ interface ThirdPartyBleSupportHandle {
274
301
  */
275
302
  declare function initThirdPartyBleSupport(webContents: WebContentsLike, options?: InitThirdPartyBleSupportOptions): ThirdPartyBleSupportHandle;
276
303
 
277
- export { type BleDebugLogEntry, type BleDebugLogLevel, type BleDebugLogger, type InitThirdPartyBleSupportOptions, type IpcMainLike, NobleBleHandler, type NobleBleHandlerOptions, type ThirdPartyBleApi, type ThirdPartyBleAvailability, type ThirdPartyBleDeviceInfo, type ThirdPartyBleSupportHandle, initThirdPartyBleSupport, redactBleDebugLogData };
304
+ export { type BleDebugLogEntry, type BleDebugLogLevel, type BleDebugLogger, DEFAULT_THIRD_PARTY_BLE_VENDORS, type InitThirdPartyBleSupportOptions, type IpcMainLike, NobleBleHandler, type NobleBleHandlerOptions, type ThirdPartyBleApi, type ThirdPartyBleAvailability, type ThirdPartyBleDeviceInfo, type ThirdPartyBleSupportHandle, type ThirdPartyBleVendorProfile, initThirdPartyBleSupport, redactBleDebugLogData };
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ DEFAULT_THIRD_PARTY_BLE_VENDORS: () => DEFAULT_THIRD_PARTY_BLE_VENDORS,
23
24
  NobleBleHandler: () => NobleBleHandler,
24
25
  THIRD_PARTY_BLE_CHANNELS: () => THIRD_PARTY_BLE_CHANNELS,
25
26
  THIRD_PARTY_BLE_DEVICE_TTL_MS: () => THIRD_PARTY_BLE_DEVICE_TTL_MS,
@@ -114,24 +115,74 @@ var THIRD_PARTY_BLE_SCAN_IDLE_STOP_MS = 1e4;
114
115
  var THIRD_PARTY_BLE_DEVICE_TTL_MS = 5e3;
115
116
  var THIRD_PARTY_BLE_POWER_ON_TIMEOUT_MS = 1e4;
116
117
 
118
+ // src/vendorProfiles.ts
119
+ var normalizeBleUuid = (uuid) => uuid.replace(/-/g, "").toLowerCase();
120
+ var isUuidString = (value) => typeof value === "string";
121
+ var isIntegerInRange = (value, min, max) => typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
122
+ var TREZOR_SERVICE_UUID = normalizeBleUuid("8c000001-a59b-4d58-a9ad-073df69fa1b1");
123
+ var TREZOR_WRITE_UUID = normalizeBleUuid("8c000002-a59b-4d58-a9ad-073df69fa1b1");
124
+ var TREZOR_NOTIFY_UUID = normalizeBleUuid("8c000003-a59b-4d58-a9ad-073df69fa1b1");
125
+ var TREZOR_PACKET_SIZE = 244;
126
+ var TREZOR_MAX_CHUNK_DELAY_MS = 50;
127
+ var TREZOR_NAME_BRAND = /\bTrezor\b/i;
128
+ var TREZOR_NAME_MODEL = /\bSafe\s*7\b|\bT3W1\b/i;
129
+ var trezorProfile = {
130
+ isServiceUuid: (uuid) => isUuidString(uuid) && normalizeBleUuid(uuid) === TREZOR_SERVICE_UUID,
131
+ isName: (name) => TREZOR_NAME_BRAND.test(name) && TREZOR_NAME_MODEL.test(name),
132
+ acceptsConnect: (options) => {
133
+ const { write } = options;
134
+ return isUuidString(options.serviceUuid) && isUuidString(options.writeUuid) && isUuidString(options.notifyUuid) && normalizeBleUuid(options.serviceUuid) === TREZOR_SERVICE_UUID && normalizeBleUuid(options.writeUuid) === TREZOR_WRITE_UUID && normalizeBleUuid(options.notifyUuid) === TREZOR_NOTIFY_UUID && typeof write === "object" && write !== null && write.mode === "padded" && write.chunkSize === TREZOR_PACKET_SIZE && (write.chunkDelayMs === void 0 || isIntegerInRange(write.chunkDelayMs, 0, TREZOR_MAX_CHUNK_DELAY_MS)) && write.maxLength === void 0;
135
+ }
136
+ };
137
+ var LEDGER_UUID = /^13d634002c97([0-9a-f]{4})000([0-3])4c6564676572$/;
138
+ var LEDGER_ROLE_SERVICE = "0";
139
+ var LEDGER_ROLE_NOTIFY = "1";
140
+ var LEDGER_ROLE_WRITE = "2";
141
+ var LEDGER_ROLE_WRITE_CMD = "3";
142
+ var LEDGER_MAX_FRAME_SIZE = 255;
143
+ var parseLedgerUuid = (uuid) => {
144
+ if (!isUuidString(uuid)) return void 0;
145
+ const parsed = LEDGER_UUID.exec(normalizeBleUuid(uuid));
146
+ return parsed ? { model: parsed[1], role: parsed[2] } : void 0;
147
+ };
148
+ var ledgerProfile = {
149
+ isServiceUuid: (uuid) => parseLedgerUuid(uuid)?.role === LEDGER_ROLE_SERVICE,
150
+ acceptsConnect: (options) => {
151
+ const service = parseLedgerUuid(options.serviceUuid);
152
+ const notify = parseLedgerUuid(options.notifyUuid);
153
+ const write = parseLedgerUuid(options.writeUuid);
154
+ const framing = options.write;
155
+ return service?.role === LEDGER_ROLE_SERVICE && notify?.role === LEDGER_ROLE_NOTIFY && (write?.role === LEDGER_ROLE_WRITE || write?.role === LEDGER_ROLE_WRITE_CMD) && notify.model === service.model && write.model === service.model && typeof framing === "object" && framing !== null && framing.mode === "raw" && isIntegerInRange(framing.maxLength, 1, LEDGER_MAX_FRAME_SIZE) && framing.chunkSize === void 0 && framing.chunkDelayMs === void 0;
156
+ }
157
+ };
158
+ var DEFAULT_THIRD_PARTY_BLE_VENDORS = Object.freeze({
159
+ ledger: ledgerProfile,
160
+ trezor: trezorProfile
161
+ });
162
+
117
163
  // src/NobleBleHandler.ts
118
164
  var DEFAULT_NOBLE_FACTORY = () => {
119
165
  const noble = require("@stoprocent/noble");
120
166
  return noble;
121
167
  };
122
- var normalizeUuid = (uuid) => uuid.replace(/-/g, "").toLowerCase();
123
- var matchesPeripheral = (p, match) => {
124
- if (!match) return false;
168
+ var BLE_NAME_MATCH_MAX_LENGTH = 64;
169
+ var matchesPeripheral = (p, profile, narrowTo) => {
125
170
  const adv = p.advertisement ?? {};
126
171
  const advertised = adv.serviceUuids ?? [];
127
- if (match.serviceUuids?.length && advertised.some(
128
- (uuid) => match.serviceUuids?.some((wanted) => normalizeUuid(wanted) === normalizeUuid(uuid))
172
+ if (advertised.some(
173
+ (uuid) => typeof uuid === "string" && profile.isServiceUuid(uuid) && (!narrowTo || narrowTo.has(normalizeBleUuid(uuid)))
129
174
  )) {
130
175
  return true;
131
176
  }
132
177
  const name = adv.localName;
133
- if (!match.namePatterns?.length || !name) return false;
134
- return match.namePatterns.every((pattern) => new RegExp(pattern, "i").test(name));
178
+ if (!profile.isName || typeof name !== "string" || !name) return false;
179
+ return profile.isName(name.slice(0, BLE_NAME_MATCH_MAX_LENGTH));
180
+ };
181
+ var requestedServiceUuids = (options) => {
182
+ const requested = options?.match ? options.match.serviceUuids : options?.serviceUuids;
183
+ if (!Array.isArray(requested)) return void 0;
184
+ const uuids = requested.filter((uuid) => typeof uuid === "string");
185
+ return uuids.length ? new Set(uuids.map(normalizeBleUuid)) : void 0;
135
186
  };
136
187
  var peripheralToInfo = (p) => {
137
188
  const adv = p.advertisement ?? {};
@@ -193,6 +244,12 @@ var NobleBleHandler = class {
193
244
  this._factory = options.nobleFactory ?? DEFAULT_NOBLE_FACTORY;
194
245
  this._connectTimeoutMs = options.connectTimeoutMs ?? BLE_CONNECT_TIMEOUT_MS;
195
246
  this._logger = options.logger;
247
+ this._vendors = new Map(Object.entries(options.vendors ?? DEFAULT_THIRD_PARTY_BLE_VENDORS));
248
+ }
249
+ _resolveVendor(vendor) {
250
+ const profile = typeof vendor === "string" ? this._vendors.get(vendor) : void 0;
251
+ if (typeof vendor !== "string" || !profile) throw new Error("Unsupported BLE vendor");
252
+ return { vendor, profile };
196
253
  }
197
254
  setNotificationListener(handler) {
198
255
  this._onNotification = handler;
@@ -245,14 +302,15 @@ var NobleBleHandler = class {
245
302
  * packet, and a Safe 7 ADV carries only its name (UUID is in the scan response).
246
303
  */
247
304
  async scan(options) {
248
- this._scanOwners.add(options?.vendor);
305
+ const { vendor, profile } = this._resolveVendor(options?.vendor);
306
+ this._scanOwners.add(vendor);
249
307
  try {
250
308
  await this.init();
251
309
  } catch (error) {
252
- this._scanOwners.delete(options?.vendor);
310
+ this._scanOwners.delete(vendor);
253
311
  throw error;
254
312
  }
255
- if (!this._scanOwners.has(options?.vendor)) return [];
313
+ if (!this._scanOwners.has(vendor)) return [];
256
314
  try {
257
315
  await this._setScanning(true);
258
316
  } catch (error) {
@@ -261,7 +319,7 @@ var NobleBleHandler = class {
261
319
  }
262
320
  this._assertActive();
263
321
  this._armIdleStop();
264
- const devices = this._snapshot(options);
322
+ const devices = this._snapshot(vendor, profile, requestedServiceUuids(options));
265
323
  if (devices.length === 0) {
266
324
  this._log("warn", "scan.empty", {
267
325
  raw: this._discovered.size,
@@ -275,9 +333,8 @@ var NobleBleHandler = class {
275
333
  * In-range devices matching the caller, minus TTL-expired ones. `_discovered`
276
334
  * is only a cache; a missing device can still be reached by `_directConnect`.
277
335
  */
278
- _snapshot(options) {
336
+ _snapshot(vendor, profile, narrowTo) {
279
337
  const now = Date.now();
280
- const match = options?.match ?? { serviceUuids: options?.serviceUuids };
281
338
  const result = [];
282
339
  for (const [id, peripheral] of this._discovered) {
283
340
  if (now - (this._lastSeen.get(id) ?? 0) > THIRD_PARTY_BLE_DEVICE_TTL_MS) {
@@ -285,11 +342,11 @@ var NobleBleHandler = class {
285
342
  this._lastSeen.delete(id);
286
343
  continue;
287
344
  }
288
- if (!matchesPeripheral(peripheral, match)) continue;
345
+ if (!matchesPeripheral(peripheral, profile, narrowTo)) continue;
289
346
  result.push(peripheralToInfo(peripheral));
290
347
  }
291
348
  for (const [id, entry] of this._connected) {
292
- if (entry.vendor !== options?.vendor) continue;
349
+ if (entry.vendor !== vendor) continue;
293
350
  if (result.some((info) => info.id === id)) continue;
294
351
  result.push(peripheralToInfo(entry.peripheral));
295
352
  }
@@ -406,7 +463,9 @@ var NobleBleHandler = class {
406
463
  */
407
464
  getDevice(id) {
408
465
  const p = this._discovered.get(id);
409
- if (!p) return null;
466
+ if (!p || ![...this._vendors.values()].some((profile) => matchesPeripheral(p, profile))) {
467
+ return null;
468
+ }
410
469
  return peripheralToInfo(p);
411
470
  }
412
471
  /** Live RSSI (dBm) of a connected peripheral, else the cached scan-time value. */
@@ -523,10 +582,13 @@ var NobleBleHandler = class {
523
582
  // `timed out` (unreachable) and `connection failed` (stale bond) differently.
524
583
  async connect(id, options) {
525
584
  if (!options || !options.vendor || ![options.serviceUuid, options.writeUuid, options.notifyUuid].every(
526
- (uuid) => typeof uuid === "string" && /^[0-9a-f]{32}$/i.test(normalizeUuid(uuid))
585
+ (uuid) => typeof uuid === "string" && /^[0-9a-f]{32}$/i.test(normalizeBleUuid(uuid))
527
586
  )) {
528
587
  throw new Error("Invalid BLE GATT profile");
529
588
  }
589
+ if (!this._resolveVendor(options.vendor).profile.acceptsConnect(options)) {
590
+ throw new Error(`BLE GATT profile not allowed for ${options.vendor}`);
591
+ }
530
592
  this._assertActive();
531
593
  const claim = { abandoned: false };
532
594
  let abandon;
@@ -634,10 +696,10 @@ var NobleBleHandler = class {
634
696
  [options.serviceUuid],
635
697
  [options.writeUuid, options.notifyUuid]
636
698
  );
637
- const writeUuid = normalizeUuid(options.writeUuid);
638
- const notifyUuid = normalizeUuid(options.notifyUuid);
639
- const writeChar = characteristics.find((c) => normalizeUuid(c.uuid) === writeUuid);
640
- const notifyChar = characteristics.find((c) => normalizeUuid(c.uuid) === notifyUuid);
699
+ const writeUuid = normalizeBleUuid(options.writeUuid);
700
+ const notifyUuid = normalizeBleUuid(options.notifyUuid);
701
+ const writeChar = characteristics.find((c) => normalizeBleUuid(c.uuid) === writeUuid);
702
+ const notifyChar = characteristics.find((c) => normalizeBleUuid(c.uuid) === notifyUuid);
641
703
  if (!writeChar || !notifyChar) {
642
704
  throw new Error(`BLE characteristics not found on device ${id}`);
643
705
  }
@@ -948,6 +1010,7 @@ function initThirdPartyBleSupport(webContents, options = {}) {
948
1010
  }
949
1011
  // Annotate the CommonJS export names for ESM import in node:
950
1012
  0 && (module.exports = {
1013
+ DEFAULT_THIRD_PARTY_BLE_VENDORS,
951
1014
  NobleBleHandler,
952
1015
  THIRD_PARTY_BLE_CHANNELS,
953
1016
  THIRD_PARTY_BLE_DEVICE_TTL_MS,