@onekeyfe/hwk-trezor-connector-electron-ble 1.2.2-alpha.8 → 1.2.2
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/main.d.mts +25 -3
- package/dist/main.d.ts +25 -3
- package/dist/main.js +206 -47
- package/dist/main.js.map +1 -1
- package/dist/main.mjs +206 -47
- package/dist/main.mjs.map +1 -1
- package/package.json +5 -5
package/dist/main.d.mts
CHANGED
|
@@ -14,6 +14,7 @@ interface NobleLike {
|
|
|
14
14
|
removeListener(event: string, handler: (...args: any[]) => void): NobleLike;
|
|
15
15
|
startScanningAsync(serviceUuids: string[], allowDuplicates: boolean): Promise<void>;
|
|
16
16
|
stopScanningAsync(): Promise<void>;
|
|
17
|
+
stop?(): void;
|
|
17
18
|
/**
|
|
18
19
|
* Connect by id/address with NO scan. Both native backends support this and
|
|
19
20
|
* emit a `discover` for the peripheral as a side effect: Windows synthesizes
|
|
@@ -22,6 +23,7 @@ interface NobleLike {
|
|
|
22
23
|
* (lib/mac/src/ble_manager.mm). Optional so a stub noble can omit it.
|
|
23
24
|
*/
|
|
24
25
|
connectAsync?(idOrAddress: string): Promise<NoblePeripheralLike | undefined>;
|
|
26
|
+
cancelConnect?(idOrAddress: string): void;
|
|
25
27
|
reset?(): Promise<void>;
|
|
26
28
|
}
|
|
27
29
|
interface NoblePeripheralLike {
|
|
@@ -44,6 +46,7 @@ interface NoblePeripheralLike {
|
|
|
44
46
|
rssi: number;
|
|
45
47
|
state: string;
|
|
46
48
|
connectAsync(): Promise<void>;
|
|
49
|
+
cancelConnect?(): void;
|
|
47
50
|
disconnectAsync(): Promise<void>;
|
|
48
51
|
discoverSomeServicesAndCharacteristicsAsync(serviceUuids: string[], characteristicUuids: string[]): Promise<{
|
|
49
52
|
characteristics: NobleCharacteristicLike[];
|
|
@@ -89,13 +92,20 @@ declare class NobleBleHandler {
|
|
|
89
92
|
private readonly _discovered;
|
|
90
93
|
private readonly _lastSeen;
|
|
91
94
|
private readonly _connected;
|
|
92
|
-
private _stateChangeHandler?;
|
|
93
95
|
private _discoverHandler?;
|
|
94
96
|
private _scanning;
|
|
95
97
|
private _idleStopTimer?;
|
|
96
98
|
private _onNotification?;
|
|
97
99
|
private _onDeviceDisconnected?;
|
|
98
100
|
private _initialized;
|
|
101
|
+
private _disposed;
|
|
102
|
+
private _disposePromise?;
|
|
103
|
+
private _releasePromise?;
|
|
104
|
+
private _initPromise?;
|
|
105
|
+
private readonly _nobleInstances;
|
|
106
|
+
private readonly _pendingCancellations;
|
|
107
|
+
private readonly _connectAttempts;
|
|
108
|
+
private _nativeReleased;
|
|
99
109
|
private _lastNobleRecoverAt?;
|
|
100
110
|
/** The connect currently in flight, so cancelPairing can abandon it. */
|
|
101
111
|
private _activeConnect?;
|
|
@@ -220,8 +230,17 @@ declare class NobleBleHandler {
|
|
|
220
230
|
subscribe(id: string): Promise<void>;
|
|
221
231
|
unsubscribe(id: string): Promise<void>;
|
|
222
232
|
write(id: string, hexData: string): Promise<void>;
|
|
223
|
-
/**
|
|
233
|
+
/** Retire a renderer's handler without stopping a process-wide native manager. */
|
|
224
234
|
dispose(): Promise<void>;
|
|
235
|
+
/**
|
|
236
|
+
* Terminal native release, including instances replaced by adapter recovery.
|
|
237
|
+
* A host sharing Noble must defer stop() until all transports have disposed,
|
|
238
|
+
* and deduplicate instances passed to releaseNoble across those transports.
|
|
239
|
+
*/
|
|
240
|
+
disposeForAppQuit(releaseNoble?: (instance: {
|
|
241
|
+
stop?(): void;
|
|
242
|
+
}) => void): Promise<void>;
|
|
243
|
+
private _assertActive;
|
|
225
244
|
private _cleanupDevice;
|
|
226
245
|
private _requireEntry;
|
|
227
246
|
private _requireNoble;
|
|
@@ -247,13 +266,16 @@ interface InitTrezorBleSupportOptions extends NobleBleHandlerOptions {
|
|
|
247
266
|
interface TrezorBleSupportHandle {
|
|
248
267
|
handler: NobleBleHandler;
|
|
249
268
|
dispose(): Promise<void>;
|
|
269
|
+
disposeForAppQuit(releaseNoble?: (instance: {
|
|
270
|
+
stop?(): void;
|
|
271
|
+
}) => void): Promise<void>;
|
|
250
272
|
}
|
|
251
273
|
/**
|
|
252
274
|
* Wire a `NobleBleHandler` to Electron's IPC so the renderer can drive BLE
|
|
253
275
|
* via `window.desktopApi.trezorBle`. Call once from the main process after
|
|
254
276
|
* `BrowserWindow` is ready.
|
|
255
277
|
*
|
|
256
|
-
*
|
|
278
|
+
* Call dispose() when retiring a renderer and disposeForAppQuit() before Node teardown.
|
|
257
279
|
*/
|
|
258
280
|
declare function initTrezorBleSupport(webContents: WebContentsLike, options?: InitTrezorBleSupportOptions): TrezorBleSupportHandle;
|
|
259
281
|
|
package/dist/main.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ interface NobleLike {
|
|
|
14
14
|
removeListener(event: string, handler: (...args: any[]) => void): NobleLike;
|
|
15
15
|
startScanningAsync(serviceUuids: string[], allowDuplicates: boolean): Promise<void>;
|
|
16
16
|
stopScanningAsync(): Promise<void>;
|
|
17
|
+
stop?(): void;
|
|
17
18
|
/**
|
|
18
19
|
* Connect by id/address with NO scan. Both native backends support this and
|
|
19
20
|
* emit a `discover` for the peripheral as a side effect: Windows synthesizes
|
|
@@ -22,6 +23,7 @@ interface NobleLike {
|
|
|
22
23
|
* (lib/mac/src/ble_manager.mm). Optional so a stub noble can omit it.
|
|
23
24
|
*/
|
|
24
25
|
connectAsync?(idOrAddress: string): Promise<NoblePeripheralLike | undefined>;
|
|
26
|
+
cancelConnect?(idOrAddress: string): void;
|
|
25
27
|
reset?(): Promise<void>;
|
|
26
28
|
}
|
|
27
29
|
interface NoblePeripheralLike {
|
|
@@ -44,6 +46,7 @@ interface NoblePeripheralLike {
|
|
|
44
46
|
rssi: number;
|
|
45
47
|
state: string;
|
|
46
48
|
connectAsync(): Promise<void>;
|
|
49
|
+
cancelConnect?(): void;
|
|
47
50
|
disconnectAsync(): Promise<void>;
|
|
48
51
|
discoverSomeServicesAndCharacteristicsAsync(serviceUuids: string[], characteristicUuids: string[]): Promise<{
|
|
49
52
|
characteristics: NobleCharacteristicLike[];
|
|
@@ -89,13 +92,20 @@ declare class NobleBleHandler {
|
|
|
89
92
|
private readonly _discovered;
|
|
90
93
|
private readonly _lastSeen;
|
|
91
94
|
private readonly _connected;
|
|
92
|
-
private _stateChangeHandler?;
|
|
93
95
|
private _discoverHandler?;
|
|
94
96
|
private _scanning;
|
|
95
97
|
private _idleStopTimer?;
|
|
96
98
|
private _onNotification?;
|
|
97
99
|
private _onDeviceDisconnected?;
|
|
98
100
|
private _initialized;
|
|
101
|
+
private _disposed;
|
|
102
|
+
private _disposePromise?;
|
|
103
|
+
private _releasePromise?;
|
|
104
|
+
private _initPromise?;
|
|
105
|
+
private readonly _nobleInstances;
|
|
106
|
+
private readonly _pendingCancellations;
|
|
107
|
+
private readonly _connectAttempts;
|
|
108
|
+
private _nativeReleased;
|
|
99
109
|
private _lastNobleRecoverAt?;
|
|
100
110
|
/** The connect currently in flight, so cancelPairing can abandon it. */
|
|
101
111
|
private _activeConnect?;
|
|
@@ -220,8 +230,17 @@ declare class NobleBleHandler {
|
|
|
220
230
|
subscribe(id: string): Promise<void>;
|
|
221
231
|
unsubscribe(id: string): Promise<void>;
|
|
222
232
|
write(id: string, hexData: string): Promise<void>;
|
|
223
|
-
/**
|
|
233
|
+
/** Retire a renderer's handler without stopping a process-wide native manager. */
|
|
224
234
|
dispose(): Promise<void>;
|
|
235
|
+
/**
|
|
236
|
+
* Terminal native release, including instances replaced by adapter recovery.
|
|
237
|
+
* A host sharing Noble must defer stop() until all transports have disposed,
|
|
238
|
+
* and deduplicate instances passed to releaseNoble across those transports.
|
|
239
|
+
*/
|
|
240
|
+
disposeForAppQuit(releaseNoble?: (instance: {
|
|
241
|
+
stop?(): void;
|
|
242
|
+
}) => void): Promise<void>;
|
|
243
|
+
private _assertActive;
|
|
225
244
|
private _cleanupDevice;
|
|
226
245
|
private _requireEntry;
|
|
227
246
|
private _requireNoble;
|
|
@@ -247,13 +266,16 @@ interface InitTrezorBleSupportOptions extends NobleBleHandlerOptions {
|
|
|
247
266
|
interface TrezorBleSupportHandle {
|
|
248
267
|
handler: NobleBleHandler;
|
|
249
268
|
dispose(): Promise<void>;
|
|
269
|
+
disposeForAppQuit(releaseNoble?: (instance: {
|
|
270
|
+
stop?(): void;
|
|
271
|
+
}) => void): Promise<void>;
|
|
250
272
|
}
|
|
251
273
|
/**
|
|
252
274
|
* Wire a `NobleBleHandler` to Electron's IPC so the renderer can drive BLE
|
|
253
275
|
* via `window.desktopApi.trezorBle`. Call once from the main process after
|
|
254
276
|
* `BrowserWindow` is ready.
|
|
255
277
|
*
|
|
256
|
-
*
|
|
278
|
+
* Call dispose() when retiring a renderer and disposeForAppQuit() before Node teardown.
|
|
257
279
|
*/
|
|
258
280
|
declare function initTrezorBleSupport(webContents: WebContentsLike, options?: InitTrezorBleSupportOptions): TrezorBleSupportHandle;
|
|
259
281
|
|
package/dist/main.js
CHANGED
|
@@ -97,6 +97,11 @@ var NobleBleHandler = class {
|
|
|
97
97
|
this._connected = /* @__PURE__ */ new Map();
|
|
98
98
|
this._scanning = false;
|
|
99
99
|
this._initialized = false;
|
|
100
|
+
this._disposed = false;
|
|
101
|
+
this._nobleInstances = /* @__PURE__ */ new Set();
|
|
102
|
+
this._pendingCancellations = /* @__PURE__ */ new Set();
|
|
103
|
+
this._connectAttempts = /* @__PURE__ */ new Set();
|
|
104
|
+
this._nativeReleased = false;
|
|
100
105
|
this._factory = options.nobleFactory ?? DEFAULT_NOBLE_FACTORY;
|
|
101
106
|
this._uuids = options.uuids ?? import_hwk_trezor_adapter.TREZOR_BLE_UUIDS;
|
|
102
107
|
this._chunkSize = options.chunkSize ?? import_hwk_trezor_adapter.TREZOR_BLE_PACKET_SIZE;
|
|
@@ -110,20 +115,34 @@ var NobleBleHandler = class {
|
|
|
110
115
|
this._onDeviceDisconnected = handler;
|
|
111
116
|
}
|
|
112
117
|
async init() {
|
|
118
|
+
this._assertActive();
|
|
113
119
|
if (this._initialized) return;
|
|
114
|
-
this.
|
|
115
|
-
this.
|
|
116
|
-
this.
|
|
117
|
-
this.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
if (this._initPromise) return this._initPromise;
|
|
121
|
+
this._initPromise = (async () => {
|
|
122
|
+
this._noble ?? (this._noble = this._factory());
|
|
123
|
+
this._nobleInstances.add(this._noble);
|
|
124
|
+
this._discoverHandler ?? (this._discoverHandler = (peripheral) => {
|
|
125
|
+
this._discovered.set(peripheral.id, peripheral);
|
|
126
|
+
this._lastSeen.set(peripheral.id, Date.now());
|
|
127
|
+
});
|
|
128
|
+
this._noble.removeListener("discover", this._discoverHandler);
|
|
129
|
+
this._noble.on("discover", this._discoverHandler);
|
|
130
|
+
await this._waitForPoweredOn(TREZOR_BLE_POWER_ON_TIMEOUT_MS);
|
|
131
|
+
this._assertActive();
|
|
132
|
+
this._initialized = true;
|
|
133
|
+
})();
|
|
134
|
+
try {
|
|
135
|
+
await this._initPromise;
|
|
136
|
+
} finally {
|
|
137
|
+
this._initPromise = void 0;
|
|
138
|
+
}
|
|
122
139
|
}
|
|
123
140
|
async checkAvailability() {
|
|
124
|
-
|
|
141
|
+
this._assertActive();
|
|
142
|
+
if (!this._noble) {
|
|
125
143
|
try {
|
|
126
144
|
this._noble = this._factory();
|
|
145
|
+
this._nobleInstances.add(this._noble);
|
|
127
146
|
} catch {
|
|
128
147
|
return { available: false, state: "unsupported", initialized: false };
|
|
129
148
|
}
|
|
@@ -164,6 +183,7 @@ var NobleBleHandler = class {
|
|
|
164
183
|
await this._recoverNobleIfStuck(String(error));
|
|
165
184
|
}
|
|
166
185
|
}
|
|
186
|
+
this._assertActive();
|
|
167
187
|
this._armIdleStop();
|
|
168
188
|
const devices = this._snapshot();
|
|
169
189
|
if (devices.length === 0) {
|
|
@@ -217,6 +237,7 @@ var NobleBleHandler = class {
|
|
|
217
237
|
* app restart that is otherwise the only cure.
|
|
218
238
|
*/
|
|
219
239
|
async _recoverNobleIfStuck(reason) {
|
|
240
|
+
if (this._disposed) return;
|
|
220
241
|
const state = this._noble?.state;
|
|
221
242
|
if (state === "poweredOn" || !this._initialized) return;
|
|
222
243
|
if (this._connected.size > 0) {
|
|
@@ -240,6 +261,7 @@ var NobleBleHandler = class {
|
|
|
240
261
|
}
|
|
241
262
|
const fresh = this._createFreshNoble();
|
|
242
263
|
this._noble = fresh;
|
|
264
|
+
this._nobleInstances.add(fresh);
|
|
243
265
|
if (this._discoverHandler) {
|
|
244
266
|
fresh.on("discover", this._discoverHandler);
|
|
245
267
|
}
|
|
@@ -253,6 +275,7 @@ var NobleBleHandler = class {
|
|
|
253
275
|
}
|
|
254
276
|
}
|
|
255
277
|
_armIdleStop() {
|
|
278
|
+
if (this._disposed) return;
|
|
256
279
|
this._clearIdleStop();
|
|
257
280
|
this._idleStopTimer = setTimeout(() => {
|
|
258
281
|
void this._stopContinuousScan();
|
|
@@ -345,6 +368,7 @@ var NobleBleHandler = class {
|
|
|
345
368
|
const finish = (p) => {
|
|
346
369
|
if (done) return;
|
|
347
370
|
done = true;
|
|
371
|
+
this._pendingCancellations.delete(cancel);
|
|
348
372
|
clearTimeout(timer);
|
|
349
373
|
noble.removeListener("discover", onDiscover);
|
|
350
374
|
void noble.stopScanningAsync().catch(() => void 0);
|
|
@@ -355,8 +379,10 @@ var NobleBleHandler = class {
|
|
|
355
379
|
if (peripheral.id === id) finish(peripheral);
|
|
356
380
|
};
|
|
357
381
|
const timer = setTimeout(() => finish(this._discovered.get(id)), timeoutMs);
|
|
382
|
+
const cancel = () => finish();
|
|
383
|
+
this._pendingCancellations.add(cancel);
|
|
358
384
|
noble.on("discover", onDiscover);
|
|
359
|
-
void noble.startScanningAsync([], false);
|
|
385
|
+
void noble.startScanningAsync([], false).catch(() => finish());
|
|
360
386
|
});
|
|
361
387
|
}
|
|
362
388
|
/**
|
|
@@ -416,10 +442,18 @@ var NobleBleHandler = class {
|
|
|
416
442
|
// waits for a CoreBluetooth disconnect event that never comes); bound it so a
|
|
417
443
|
// cleanup disconnect can't hang the connect flow.
|
|
418
444
|
async _safeDisconnect(peripheral) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
445
|
+
if (this._nativeReleased) return;
|
|
446
|
+
let timeout;
|
|
447
|
+
try {
|
|
448
|
+
await Promise.race([
|
|
449
|
+
peripheral.disconnectAsync().catch(() => void 0),
|
|
450
|
+
new Promise((resolve) => {
|
|
451
|
+
timeout = setTimeout(resolve, BLE_DISCONNECT_TIMEOUT_MS);
|
|
452
|
+
})
|
|
453
|
+
]);
|
|
454
|
+
} finally {
|
|
455
|
+
clearTimeout(timeout);
|
|
456
|
+
}
|
|
423
457
|
}
|
|
424
458
|
// noble has no connect timeout, so a stale bond hangs anywhere — connectAsync
|
|
425
459
|
// OR the post-connect (encrypted) service discovery. One overall timeout
|
|
@@ -427,6 +461,7 @@ var NobleBleHandler = class {
|
|
|
427
461
|
// out` reject (device unreachable) vs a connectAsync `connection failed`
|
|
428
462
|
// reject (link refused / stale bond) — mapped to different error codes there.
|
|
429
463
|
async connect(id) {
|
|
464
|
+
this._assertActive();
|
|
430
465
|
const claim = { abandoned: false };
|
|
431
466
|
let abandon;
|
|
432
467
|
const abandoned = new Promise((_, reject) => {
|
|
@@ -439,23 +474,37 @@ var NobleBleHandler = class {
|
|
|
439
474
|
() => abandon(new Error(`connect timed out after ${this._connectTimeoutMs}ms`)),
|
|
440
475
|
this._connectTimeoutMs
|
|
441
476
|
);
|
|
442
|
-
const attempt = {
|
|
477
|
+
const attempt = {
|
|
478
|
+
id,
|
|
479
|
+
abandon,
|
|
480
|
+
cancelNative: () => claim.cancelNative?.(),
|
|
481
|
+
settled: Promise.resolve(void 0)
|
|
482
|
+
};
|
|
443
483
|
this._activeConnect = attempt;
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
484
|
+
this._connectAttempts.add(attempt);
|
|
485
|
+
const nativeOperation = this._connectInner(id, claim);
|
|
486
|
+
const caller = (async () => {
|
|
487
|
+
try {
|
|
488
|
+
return await Promise.race([nativeOperation, abandoned]);
|
|
489
|
+
} catch (error) {
|
|
490
|
+
const peripheral = this._discovered.get(id);
|
|
491
|
+
if (peripheral) await this._safeDisconnect(peripheral);
|
|
492
|
+
throw error;
|
|
493
|
+
} finally {
|
|
494
|
+
clearTimeout(timer);
|
|
495
|
+
if (this._activeConnect === attempt) this._activeConnect = void 0;
|
|
496
|
+
}
|
|
497
|
+
})();
|
|
498
|
+
attempt.settled = Promise.allSettled([nativeOperation, caller]).finally(() => {
|
|
499
|
+
this._connectAttempts.delete(attempt);
|
|
500
|
+
});
|
|
501
|
+
return caller;
|
|
454
502
|
}
|
|
455
503
|
async _connectInner(id, claim) {
|
|
456
504
|
await this.init();
|
|
457
505
|
await this._pauseScan();
|
|
458
506
|
await delay(BLE_CONNECT_SETTLE_MS);
|
|
507
|
+
this._assertActive();
|
|
459
508
|
let route = "cache";
|
|
460
509
|
let peripheral = this._discovered.get(id);
|
|
461
510
|
if (!peripheral) {
|
|
@@ -466,6 +515,8 @@ var NobleBleHandler = class {
|
|
|
466
515
|
route = "direct";
|
|
467
516
|
}
|
|
468
517
|
if (!peripheral) {
|
|
518
|
+
const native2 = this._requireNoble();
|
|
519
|
+
claim.cancelNative = () => native2.cancelConnect?.(id);
|
|
469
520
|
peripheral = await this._directConnect(id);
|
|
470
521
|
}
|
|
471
522
|
if (!peripheral) {
|
|
@@ -478,7 +529,7 @@ var NobleBleHandler = class {
|
|
|
478
529
|
throw new Error(`Trezor BLE device not found: ${id}`);
|
|
479
530
|
}
|
|
480
531
|
const abortIfAbandoned = async (stage) => {
|
|
481
|
-
if (!claim.abandoned) return;
|
|
532
|
+
if (!claim.abandoned && !this._disposed) return;
|
|
482
533
|
if (peripheral && peripheral.state === "connected" && !this._connected.has(id)) {
|
|
483
534
|
await this._safeDisconnect(peripheral);
|
|
484
535
|
}
|
|
@@ -487,6 +538,15 @@ var NobleBleHandler = class {
|
|
|
487
538
|
};
|
|
488
539
|
await abortIfAbandoned("resolve");
|
|
489
540
|
const wasConnected = peripheral.state === "connected";
|
|
541
|
+
const connectingPeripheral = peripheral;
|
|
542
|
+
const native = this._requireNoble();
|
|
543
|
+
claim.cancelNative = () => {
|
|
544
|
+
if (connectingPeripheral.state === "connecting" && connectingPeripheral.cancelConnect) {
|
|
545
|
+
connectingPeripheral.cancelConnect();
|
|
546
|
+
} else {
|
|
547
|
+
native.cancelConnect?.(id);
|
|
548
|
+
}
|
|
549
|
+
};
|
|
490
550
|
this._log("warn", "connect.route", {
|
|
491
551
|
id,
|
|
492
552
|
route,
|
|
@@ -572,6 +632,7 @@ var NobleBleHandler = class {
|
|
|
572
632
|
if (!entry.writeChar) throw new Error(`Trezor BLE write char missing for ${id}`);
|
|
573
633
|
const buffer = Buffer.from(hexData, "hex");
|
|
574
634
|
for (let offset = 0; offset < buffer.length; offset += this._chunkSize) {
|
|
635
|
+
this._assertActive();
|
|
575
636
|
const slice = buffer.subarray(offset, offset + this._chunkSize);
|
|
576
637
|
const chunk = Buffer.alloc(this._chunkSize);
|
|
577
638
|
slice.copy(chunk);
|
|
@@ -581,22 +642,98 @@ var NobleBleHandler = class {
|
|
|
581
642
|
}
|
|
582
643
|
}
|
|
583
644
|
}
|
|
584
|
-
/**
|
|
585
|
-
|
|
645
|
+
/** Retire a renderer's handler without stopping a process-wide native manager. */
|
|
646
|
+
dispose() {
|
|
647
|
+
if (this._disposePromise) return this._disposePromise;
|
|
648
|
+
this._disposed = true;
|
|
586
649
|
this._clearIdleStop();
|
|
587
650
|
this._scanning = false;
|
|
588
|
-
|
|
589
|
-
|
|
651
|
+
this._onNotification = void 0;
|
|
652
|
+
this._onDeviceDisconnected = void 0;
|
|
653
|
+
const connections = Array.from(this._connectAttempts);
|
|
654
|
+
for (const attempt of connections) {
|
|
655
|
+
attempt.abandon(new Error("Trezor BLE is shutting down"));
|
|
656
|
+
try {
|
|
657
|
+
attempt.cancelNative();
|
|
658
|
+
} catch (error) {
|
|
659
|
+
this._log("warn", "dispose.cancelConnect.error", { error: String(error) });
|
|
660
|
+
}
|
|
590
661
|
}
|
|
662
|
+
for (const cancel of this._pendingCancellations) cancel();
|
|
591
663
|
if (this._noble && this._discoverHandler) {
|
|
592
664
|
this._noble.removeListener("discover", this._discoverHandler);
|
|
593
665
|
}
|
|
594
|
-
|
|
595
|
-
|
|
666
|
+
const entries = Array.from(this._connected.entries());
|
|
667
|
+
for (const [id, entry] of entries) {
|
|
668
|
+
if (entry.disconnectHandler) {
|
|
669
|
+
entry.peripheral.removeListener("disconnect", entry.disconnectHandler);
|
|
670
|
+
}
|
|
671
|
+
this._cleanupDevice(id, false);
|
|
596
672
|
}
|
|
597
|
-
|
|
598
|
-
this.
|
|
599
|
-
|
|
673
|
+
let timeout;
|
|
674
|
+
this._disposePromise = (async () => {
|
|
675
|
+
try {
|
|
676
|
+
await Promise.race([
|
|
677
|
+
Promise.allSettled([
|
|
678
|
+
...connections.map((attempt) => attempt.settled),
|
|
679
|
+
...Array.from(this._nobleInstances, async (instance) => instance.stopScanningAsync()),
|
|
680
|
+
...entries.map(async ([, entry]) => {
|
|
681
|
+
let unsubscribeTimeout;
|
|
682
|
+
try {
|
|
683
|
+
await Promise.race([
|
|
684
|
+
entry.notifyChar?.unsubscribeAsync().catch(() => void 0),
|
|
685
|
+
new Promise((resolve) => {
|
|
686
|
+
unsubscribeTimeout = setTimeout(resolve, 250);
|
|
687
|
+
})
|
|
688
|
+
]);
|
|
689
|
+
} finally {
|
|
690
|
+
clearTimeout(unsubscribeTimeout);
|
|
691
|
+
await this._safeDisconnect(entry.peripheral);
|
|
692
|
+
}
|
|
693
|
+
})
|
|
694
|
+
]),
|
|
695
|
+
new Promise((resolve) => {
|
|
696
|
+
timeout = setTimeout(() => {
|
|
697
|
+
this._log("warn", "dispose.timeout");
|
|
698
|
+
resolve();
|
|
699
|
+
}, 3500);
|
|
700
|
+
})
|
|
701
|
+
]);
|
|
702
|
+
} finally {
|
|
703
|
+
clearTimeout(timeout);
|
|
704
|
+
this._discovered.clear();
|
|
705
|
+
this._lastSeen.clear();
|
|
706
|
+
this._initialized = false;
|
|
707
|
+
}
|
|
708
|
+
})();
|
|
709
|
+
return this._disposePromise;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Terminal native release, including instances replaced by adapter recovery.
|
|
713
|
+
* A host sharing Noble must defer stop() until all transports have disposed,
|
|
714
|
+
* and deduplicate instances passed to releaseNoble across those transports.
|
|
715
|
+
*/
|
|
716
|
+
disposeForAppQuit(releaseNoble = (instance) => instance.stop?.()) {
|
|
717
|
+
if (!this._releasePromise) {
|
|
718
|
+
this._releasePromise = this.dispose().finally(() => {
|
|
719
|
+
this._nativeReleased = true;
|
|
720
|
+
let releaseError;
|
|
721
|
+
for (const instance of this._nobleInstances) {
|
|
722
|
+
try {
|
|
723
|
+
releaseNoble(instance);
|
|
724
|
+
} catch (error) {
|
|
725
|
+
releaseError = error instanceof Error ? error : new Error(String(error));
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
this._nobleInstances.clear();
|
|
729
|
+
if (releaseError) throw releaseError;
|
|
730
|
+
this._log("info", "dispose.native.done");
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
return this._releasePromise;
|
|
734
|
+
}
|
|
735
|
+
_assertActive() {
|
|
736
|
+
if (this._disposed) throw new Error("Trezor BLE is shutting down");
|
|
600
737
|
}
|
|
601
738
|
_cleanupDevice(id, unexpected) {
|
|
602
739
|
const entry = this._connected.get(id);
|
|
@@ -611,11 +748,13 @@ var NobleBleHandler = class {
|
|
|
611
748
|
}
|
|
612
749
|
}
|
|
613
750
|
_requireEntry(id) {
|
|
751
|
+
this._assertActive();
|
|
614
752
|
const entry = this._connected.get(id);
|
|
615
753
|
if (!entry) throw new Error(`Trezor BLE device is not connected: ${id}`);
|
|
616
754
|
return entry;
|
|
617
755
|
}
|
|
618
756
|
_requireNoble() {
|
|
757
|
+
this._assertActive();
|
|
619
758
|
if (!this._noble) throw new Error("Trezor BLE: noble was not initialized");
|
|
620
759
|
return this._noble;
|
|
621
760
|
}
|
|
@@ -623,8 +762,17 @@ var NobleBleHandler = class {
|
|
|
623
762
|
const noble = this._requireNoble();
|
|
624
763
|
if (noble.state === "poweredOn") return;
|
|
625
764
|
await new Promise((resolve, reject) => {
|
|
765
|
+
const cleanup = () => {
|
|
766
|
+
clearTimeout(timer);
|
|
767
|
+
noble.removeListener("stateChange", handler);
|
|
768
|
+
this._pendingCancellations.delete(cancel);
|
|
769
|
+
};
|
|
770
|
+
const cancel = () => {
|
|
771
|
+
cleanup();
|
|
772
|
+
reject(new Error("Trezor BLE is shutting down"));
|
|
773
|
+
};
|
|
626
774
|
const timer = setTimeout(() => {
|
|
627
|
-
|
|
775
|
+
cleanup();
|
|
628
776
|
reject(
|
|
629
777
|
new Error(
|
|
630
778
|
`Trezor BLE: noble did not reach poweredOn within ${timeoutMs}ms (last state: ${noble.state})`
|
|
@@ -633,16 +781,14 @@ var NobleBleHandler = class {
|
|
|
633
781
|
}, timeoutMs);
|
|
634
782
|
const handler = (state) => {
|
|
635
783
|
if (state === "poweredOn") {
|
|
636
|
-
|
|
784
|
+
cleanup();
|
|
637
785
|
resolve();
|
|
638
786
|
} else if (state === "unsupported" || state === "unauthorized") {
|
|
639
|
-
|
|
640
|
-
if (this._stateChangeHandler)
|
|
641
|
-
noble.removeListener("stateChange", this._stateChangeHandler);
|
|
787
|
+
cleanup();
|
|
642
788
|
reject(new Error(`Trezor BLE: noble state ${state}`));
|
|
643
789
|
}
|
|
644
790
|
};
|
|
645
|
-
this.
|
|
791
|
+
this._pendingCancellations.add(cancel);
|
|
646
792
|
noble.on("stateChange", handler);
|
|
647
793
|
});
|
|
648
794
|
}
|
|
@@ -661,6 +807,7 @@ var DEFAULT_IPC_MAIN = () => {
|
|
|
661
807
|
function initTrezorBleSupport(webContents, options = {}) {
|
|
662
808
|
const ipcMain = options.ipcMain ?? DEFAULT_IPC_MAIN();
|
|
663
809
|
const handler = new NobleBleHandler(options);
|
|
810
|
+
let disposed = false;
|
|
664
811
|
handler.setNotificationListener((id, hexData) => {
|
|
665
812
|
webContents.send(TREZOR_BLE_CHANNELS.notification, id, hexData);
|
|
666
813
|
});
|
|
@@ -668,7 +815,10 @@ function initTrezorBleSupport(webContents, options = {}) {
|
|
|
668
815
|
webContents.send(TREZOR_BLE_CHANNELS.disconnected, id);
|
|
669
816
|
});
|
|
670
817
|
const handle = (channel, fn) => {
|
|
671
|
-
ipcMain.handle(channel, async (_event, ...args) =>
|
|
818
|
+
ipcMain.handle(channel, async (_event, ...args) => {
|
|
819
|
+
if (disposed) throw new Error("Trezor BLE is shutting down");
|
|
820
|
+
return fn(...args);
|
|
821
|
+
});
|
|
672
822
|
};
|
|
673
823
|
handle(
|
|
674
824
|
TREZOR_BLE_CHANNELS.scan,
|
|
@@ -684,13 +834,22 @@ function initTrezorBleSupport(webContents, options = {}) {
|
|
|
684
834
|
handle(TREZOR_BLE_CHANNELS.getDevice, (id) => handler.getDevice(id));
|
|
685
835
|
handle(TREZOR_BLE_CHANNELS.readRssi, (id) => handler.readRssi(id));
|
|
686
836
|
handle(TREZOR_BLE_CHANNELS.cancelPairing, () => handler.cancelPairing());
|
|
837
|
+
const removeHandlers = () => {
|
|
838
|
+
if (disposed) return;
|
|
839
|
+
disposed = true;
|
|
840
|
+
for (const channel of Object.values(TREZOR_BLE_CHANNELS)) {
|
|
841
|
+
ipcMain.removeHandler(channel);
|
|
842
|
+
}
|
|
843
|
+
};
|
|
687
844
|
return {
|
|
688
845
|
handler,
|
|
689
|
-
dispose:
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
846
|
+
dispose: () => {
|
|
847
|
+
removeHandlers();
|
|
848
|
+
return handler.dispose();
|
|
849
|
+
},
|
|
850
|
+
disposeForAppQuit: (releaseNoble) => {
|
|
851
|
+
removeHandlers();
|
|
852
|
+
return handler.disposeForAppQuit(releaseNoble);
|
|
694
853
|
}
|
|
695
854
|
};
|
|
696
855
|
}
|