@onekeyfe/hd-transport-web-device 1.2.0-alpha.98 → 1.2.0

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.
@@ -0,0 +1,276 @@
1
+ import transport from '@onekeyfe/hd-transport';
2
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
3
+
4
+ import WebUsbTransport from '../src/webusb';
5
+
6
+ const schema = {
7
+ nested: {
8
+ Ping: { fields: { message: { type: 'string', id: 1 } } },
9
+ Success: { fields: { message: { type: 'string', id: 1 } } },
10
+ MessageType: {
11
+ values: {
12
+ MessageType_Ping: 60206,
13
+ MessageType_Success: 60207,
14
+ },
15
+ },
16
+ },
17
+ };
18
+
19
+ function buildAcquirableTransport(path = 'pro-webusb') {
20
+ const webusb = new WebUsbTransport() as any;
21
+ webusb.Log = { debug: jest.fn() };
22
+ webusb.rotateProtocolV2UsbGeneration = jest.fn().mockResolvedValue(undefined);
23
+ webusb.closeOpenDevice = jest.fn().mockResolvedValue(undefined);
24
+ webusb.connect = jest.fn().mockResolvedValue(undefined);
25
+ // Simulate a device that stayed connected since the probe: same USBDevice
26
+ // object present in the device list and recorded as the probed object.
27
+ const deviceObject = { serialNumber: path } as unknown as USBDevice;
28
+ webusb.deviceList = [{ path, device: deviceObject, commType: 'webusb' }];
29
+ webusb.probedDeviceObjects.set(path, deviceObject);
30
+ return webusb;
31
+ }
32
+
33
+ describe('WebUsbTransport protocol probe cache', () => {
34
+ test('acquire skips the wire probe when the protocol is already cached', async () => {
35
+ const webusb = buildAcquirableTransport();
36
+ const path = 'pro-webusb';
37
+ webusb.deviceProtocol.set(path, 'V1');
38
+ webusb.detectProtocol = jest.fn();
39
+
40
+ await expect(webusb.acquire({ path, expectedProtocol: 'V1' })).resolves.toBe(path);
41
+
42
+ expect(webusb.detectProtocol).not.toHaveBeenCalled();
43
+ expect(webusb.deviceProtocol.get(path)).toBe('V1');
44
+ expect(webusb.acquiredPaths.has(path)).toBe(true);
45
+ });
46
+
47
+ test('acquire re-probes when the caller expects a different protocol than cached', async () => {
48
+ const webusb = buildAcquirableTransport();
49
+ const path = 'pro-webusb';
50
+ webusb.deviceProtocol.set(path, 'V1');
51
+ webusb.detectProtocol = jest.fn().mockImplementation((p: string) => {
52
+ webusb.deviceProtocol.set(p, 'V2');
53
+ return Promise.resolve('V2');
54
+ });
55
+
56
+ await expect(webusb.acquire({ path, expectedProtocol: 'V2' })).resolves.toBe(path);
57
+
58
+ expect(webusb.detectProtocol).toHaveBeenCalledWith(path, 'V2', undefined);
59
+ expect(webusb.deviceProtocol.get(path)).toBe('V2');
60
+ });
61
+
62
+ test('acquire probes when nothing is cached for the path', async () => {
63
+ const webusb = buildAcquirableTransport();
64
+ const path = 'pro-webusb';
65
+ webusb.detectProtocol = jest.fn().mockResolvedValue('V1');
66
+
67
+ await expect(webusb.acquire({ path, expectedProtocol: 'V1' })).resolves.toBe(path);
68
+
69
+ expect(webusb.detectProtocol).toHaveBeenCalledWith(path, 'V1', undefined);
70
+ });
71
+
72
+ test('acquire reuses a previously confirmed protocol during explicit no-probe recovery', async () => {
73
+ const webusb = buildAcquirableTransport();
74
+ const path = 'pro-webusb';
75
+ webusb.deviceProtocol.set(path, 'V1');
76
+ webusb.confirmedDeviceProtocols.set(path, 'V2');
77
+ webusb.markProtocolStale(path);
78
+ webusb.detectProtocol = jest.fn();
79
+
80
+ await expect(
81
+ webusb.acquire({ path, expectedProtocol: 'V2', skipProtocolProbe: true })
82
+ ).resolves.toBe(path);
83
+
84
+ expect(webusb.detectProtocol).not.toHaveBeenCalled();
85
+ expect(webusb.deviceProtocol.get(path)).toBe('V2');
86
+ expect(webusb.staleProtocolPaths.has(path)).toBe(false);
87
+ expect(webusb.acquiredPaths.has(path)).toBe(true);
88
+ });
89
+
90
+ test('acquire rejects no-probe recovery without a previously confirmed protocol', async () => {
91
+ const webusb = buildAcquirableTransport();
92
+ const path = 'pro-webusb';
93
+ webusb.detectProtocol = jest.fn();
94
+
95
+ await expect(
96
+ webusb.acquire({ path, expectedProtocol: 'V2', skipProtocolProbe: true })
97
+ ).rejects.toThrow('previously confirmed protocol');
98
+
99
+ expect(webusb.detectProtocol).not.toHaveBeenCalled();
100
+ expect(webusb.deviceProtocol.has(path)).toBe(false);
101
+ expect(webusb.acquiredPaths.has(path)).toBe(false);
102
+ });
103
+
104
+ test('forced protocol detection bypasses a valid cache and probes on the wire', async () => {
105
+ const webusb = buildAcquirableTransport();
106
+ const path = 'pro-webusb';
107
+ // Valid cache AND unchanged USBDevice object — the strongest cache hit.
108
+ webusb.deviceProtocol.set(path, 'V1');
109
+ webusb.detectProtocol = jest.fn().mockImplementation((p: string) => {
110
+ webusb.deviceProtocol.set(p, 'V1');
111
+ return Promise.resolve('V1');
112
+ });
113
+
114
+ await expect(webusb.acquire({ path, forceProtocolDetection: true })).resolves.toBe(path);
115
+
116
+ // Explicit recovery must always reach the wire probe.
117
+ expect(webusb.detectProtocol).toHaveBeenCalledTimes(1);
118
+ });
119
+
120
+ test('acquire re-probes when the USBDevice object identity changed since the probe', async () => {
121
+ const webusb = buildAcquirableTransport();
122
+ const path = 'pro-webusb';
123
+ webusb.deviceProtocol.set(path, 'V1');
124
+ // Simulate a replug the transport never saw a disconnect event for: the OS
125
+ // re-enumerated the device, so the list now holds a NEW USBDevice object.
126
+ webusb.deviceList = [
127
+ { path, device: { serialNumber: path } as unknown as USBDevice, commType: 'webusb' },
128
+ ];
129
+ webusb.detectProtocol = jest.fn().mockImplementation((p: string) => {
130
+ webusb.deviceProtocol.set(p, 'V1');
131
+ return Promise.resolve('V1');
132
+ });
133
+
134
+ await expect(webusb.acquire({ path, expectedProtocol: 'V1' })).resolves.toBe(path);
135
+
136
+ expect(webusb.detectProtocol).toHaveBeenCalledTimes(1);
137
+ });
138
+
139
+ test('acquire re-probes a stale-marked path even when a protocol is cached', async () => {
140
+ const webusb = buildAcquirableTransport();
141
+ const path = 'pro-webusb';
142
+ webusb.deviceProtocol.set(path, 'V1');
143
+ webusb.markProtocolStale(path);
144
+ webusb.detectProtocol = jest.fn().mockImplementation((p: string) => {
145
+ webusb.deviceProtocol.set(p, 'V1');
146
+ return Promise.resolve('V1');
147
+ });
148
+
149
+ await expect(webusb.acquire({ path, expectedProtocol: 'V1' })).resolves.toBe(path);
150
+
151
+ expect(webusb.detectProtocol).toHaveBeenCalledTimes(1);
152
+ expect(webusb.staleProtocolPaths.has(path)).toBe(false);
153
+ });
154
+
155
+ test('release keeps the cached V1 protocol so the next acquire can reuse it', async () => {
156
+ const webusb = new WebUsbTransport() as any;
157
+ const path = 'pro-webusb';
158
+ webusb.deviceProtocol.set(path, 'V1');
159
+ webusb.deviceProtocolHints.set(path, 'V2');
160
+ webusb.deviceEndpoints.set(path, { interfaceNumber: 0, endpointIn: 1, endpointOut: 1 });
161
+ webusb.acquiredPaths.add(path);
162
+ webusb.closeOpenDevice = jest.fn().mockResolvedValue(undefined);
163
+
164
+ await webusb.release(path);
165
+
166
+ expect(webusb.deviceProtocol.get(path)).toBe('V1');
167
+ expect(webusb.deviceProtocolHints.get(path)).toBe('V2');
168
+ expect(webusb.deviceEndpoints.has(path)).toBe(false);
169
+ expect(webusb.acquiredPaths.has(path)).toBe(false);
170
+ });
171
+
172
+ test('release still drops a cached V2 protocol through link invalidation', async () => {
173
+ const webusb = new WebUsbTransport() as any;
174
+ const path = 'pro2-webusb';
175
+ webusb.Log = { debug: jest.fn() };
176
+ webusb.messages = transport.parseConfigure(schema);
177
+ webusb.messagesV2 = transport.parseConfigure(schema);
178
+ webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
179
+ let markReadStarted: () => void = () => undefined;
180
+ const readStarted = new Promise<void>(resolve => {
181
+ markReadStarted = resolve;
182
+ });
183
+ webusb.readProtocolV2UsbPacket = jest.fn().mockImplementation(() => {
184
+ markReadStarted();
185
+ return new Promise<void>(() => {});
186
+ });
187
+ webusb.closeOpenDevice = jest.fn().mockResolvedValue(undefined);
188
+ webusb.deviceProtocol.set(path, 'V2');
189
+ await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
190
+
191
+ const call = webusb.callProtocolV2(path, 'Ping', { message: 'release' });
192
+ await readStarted;
193
+ await webusb.release(path);
194
+
195
+ await expect(call).rejects.toThrow('WebUSB transport released');
196
+ expect(webusb.deviceProtocol.has(path)).toBe(false);
197
+ });
198
+
199
+ test('call and post fail fast for a path that is not acquired', async () => {
200
+ const webusb = new WebUsbTransport() as any;
201
+ const path = 'pro-webusb';
202
+ webusb.Log = { debug: jest.fn() };
203
+ webusb.messages = transport.parseConfigure(schema);
204
+ webusb.messagesV2 = transport.parseConfigure(schema);
205
+ // A surviving protocol cache entry must NOT act as a session token.
206
+ webusb.deviceProtocol.set(path, 'V1');
207
+
208
+ await expect(webusb.call(path, 'Ping', {})).rejects.toMatchObject({
209
+ errorCode: HardwareErrorCode.RuntimeError,
210
+ message: expect.stringContaining('not acquired'),
211
+ });
212
+ await expect(webusb.post(path, 'Ping', {})).rejects.toMatchObject({
213
+ errorCode: HardwareErrorCode.RuntimeError,
214
+ message: expect.stringContaining('not acquired'),
215
+ });
216
+ });
217
+
218
+ test('a transfer-level reconnect marks the protocol stale for the next acquire', async () => {
219
+ const webusb = new WebUsbTransport() as any;
220
+ const path = 'pro-webusb';
221
+ webusb.Log = { debug: jest.fn() };
222
+ webusb.deviceProtocol.set(path, 'V1');
223
+ webusb.findDevice = jest.fn().mockResolvedValue({ opened: false });
224
+ webusb.getConnectedDevices = jest.fn().mockResolvedValue([]);
225
+ webusb.connect = jest.fn().mockResolvedValue(undefined);
226
+
227
+ await webusb.reconnectForPacketIoRetry(path, 'in', 0, new Error('transferIn failed'));
228
+
229
+ // The in-flight call keeps the cached protocol; only the next acquire re-probes.
230
+ expect(webusb.deviceProtocol.get(path)).toBe('V1');
231
+ expect(webusb.staleProtocolPaths.has(path)).toBe(true);
232
+ });
233
+
234
+ test('USB disconnect marks the serial stale and the listener attaches only once', () => {
235
+ const addEventListener = jest.fn();
236
+ const usb = { addEventListener } as unknown as USB;
237
+ const originalNavigator = (globalThis as any).navigator;
238
+ Object.defineProperty(globalThis, 'navigator', {
239
+ value: { usb },
240
+ configurable: true,
241
+ });
242
+ try {
243
+ const first = new WebUsbTransport() as any;
244
+ first.init({ debug: jest.fn() });
245
+ const second = new WebUsbTransport() as any;
246
+ second.init({ debug: jest.fn() });
247
+
248
+ // Module-level listener: one registration across instances.
249
+ expect(addEventListener).toHaveBeenCalledTimes(1);
250
+ const handler = addEventListener.mock.calls[0][1] as (event: {
251
+ device?: { serialNumber?: string | null };
252
+ }) => void;
253
+
254
+ const path = 'pro-webusb';
255
+ first.deviceProtocol.set(path, 'V1');
256
+ second.deviceProtocol.set(path, 'V1');
257
+ handler({ device: { serialNumber: path } });
258
+
259
+ // Routed to the most recently initialized instance; the cached value is
260
+ // retained (in-flight sessions keep working) but marked stale.
261
+ expect(second.staleProtocolPaths.has(path)).toBe(true);
262
+ expect(second.deviceProtocol.get(path)).toBe('V1');
263
+ expect(first.staleProtocolPaths.has(path)).toBe(false);
264
+
265
+ // Events without a device identity are ignored.
266
+ handler({ device: { serialNumber: null } });
267
+ handler({});
268
+ expect(second.staleProtocolPaths.size).toBe(1);
269
+ } finally {
270
+ Object.defineProperty(globalThis, 'navigator', {
271
+ value: originalNavigator,
272
+ configurable: true,
273
+ });
274
+ }
275
+ });
276
+ });
@@ -21,8 +21,9 @@ const schema = {
21
21
  };
22
22
 
23
23
  describe('WebUsbTransport Protocol V2 timeout recovery', () => {
24
- test('only enumerates devices with real USB serial numbers', async () => {
25
- const filter = ONEKEY_WEBUSB_FILTER[0];
24
+ test('enumerates OneKey USB devices even when the serial string is omitted', async () => {
25
+ const filter =
26
+ ONEKEY_WEBUSB_FILTER.find(item => item.productId === 0x4f4c) ?? ONEKEY_WEBUSB_FILTER[0];
26
27
  const deviceWithSerial = {
27
28
  ...filter,
28
29
  manufacturerName: 'OneKey',
@@ -32,7 +33,7 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
32
33
  const deviceWithoutSerial = {
33
34
  ...filter,
34
35
  manufacturerName: 'OneKey',
35
- productName: 'OneKey Pro 2',
36
+ productName: 'OneKey Neo',
36
37
  serialNumber: null,
37
38
  } as USBDevice;
38
39
  const webusb = new WebUsbTransport();
@@ -46,6 +47,11 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
46
47
  device: deviceWithSerial,
47
48
  commType: 'webusb',
48
49
  },
50
+ {
51
+ path: 'usb-1209-4f4c-onekey-neo',
52
+ device: deviceWithoutSerial,
53
+ commType: 'webusb',
54
+ },
49
55
  ]);
50
56
  });
51
57
 
@@ -148,7 +154,7 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
148
154
  expect(webusb.deviceProtocol.get(path)).toBe('V2');
149
155
  });
150
156
 
151
- test('reports a Protocol V2 probe timeout only after the bounded retry is exhausted', async () => {
157
+ test('reports a device initialization failure only after the bounded retry is exhausted', async () => {
152
158
  const webusb = new WebUsbTransport() as any;
153
159
  const path = 'pro2-webusb';
154
160
  webusb.probeProtocolV1 = jest.fn();
@@ -156,9 +162,10 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
156
162
  webusb.resetConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
157
163
  webusb.closeConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
158
164
 
159
- await expect(webusb.detectProtocol(path, 'V2')).rejects.toThrow(
160
- 'Protocol V2 probe timeout after 2 attempts'
161
- );
165
+ await expect(webusb.detectProtocol(path, 'V2')).rejects.toMatchObject({
166
+ errorCode: HardwareErrorCode.DeviceInitializeFailed,
167
+ message: 'Protocol V2 probe timeout after 2 attempts',
168
+ });
162
169
 
163
170
  expect(webusb.probeProtocolV2).toHaveBeenCalledTimes(2);
164
171
  expect(webusb.probeProtocolV1).not.toHaveBeenCalled();
@@ -380,6 +387,13 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
380
387
  webusb.callProtocolV2(path, 'Ping', { message: 'short' }, { timeoutMs: 25 }),
381
388
  ]);
382
389
 
383
- expect(readTimeouts).toEqual([1_000, 25]);
390
+ // Each call keeps its own budget instead of inheriting the other's. The
391
+ // queued one is a deadline, so it arrives with whatever the first call left
392
+ // of its 25ms — asserting the exact remainder makes this fail on a loaded
393
+ // machine, which is timing, not behaviour.
394
+ expect(readTimeouts).toHaveLength(2);
395
+ expect(readTimeouts[0]).toBe(1_000);
396
+ expect(readTimeouts[1]).toBeGreaterThan(0);
397
+ expect(readTimeouts[1]).toBeLessThanOrEqual(25);
384
398
  });
385
399
  });
@@ -27,6 +27,7 @@ export default class ElectronBleTransport {
27
27
  private connectedDevices;
28
28
  private deviceProtocol;
29
29
  private deviceProtocolHints;
30
+ private confirmedProtocolV2;
30
31
  private deviceMtus;
31
32
  private devicePacketCapacities;
32
33
  private v1Buffers;
@@ -37,12 +38,14 @@ export default class ElectronBleTransport {
37
38
  private warnedMissingRelease;
38
39
  private notificationCleanups;
39
40
  private mtuCleanups;
40
- private disconnectCleanups;
41
+ private hostDisconnectCleanup?;
41
42
  private notificationTokens;
42
43
  private nextNotificationToken;
44
+ private toStaleBondError;
43
45
  private handleBluetoothError;
44
46
  private cleanupDeviceState;
45
47
  init(logger: any, emitter?: EventEmitter): void;
48
+ private subscribeHostDisconnects;
46
49
  configure(signedData: any): void;
47
50
  configureProtocolV2(signedData: any): void;
48
51
  listen(): Promise<OneKeyDeviceInfo[]>;
@@ -76,6 +79,7 @@ export default class ElectronBleTransport {
76
79
  private createMtuSubscription;
77
80
  private writeProtocolV2Frame;
78
81
  private handleNotification;
82
+ private readProtocolV2LinkDisabledFailure;
79
83
  private handleProtocolV2Notification;
80
84
  private getProtocolV2FrameQueue;
81
85
  private resolveProtocolV2Frame;
@@ -84,6 +88,7 @@ export default class ElectronBleTransport {
84
88
  private readProtocolV2Frame;
85
89
  private handleProtocolV1Notification;
86
90
  call(uuid: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<import("@onekeyfe/hd-transport").MessageFromOneKey>;
91
+ post(uuid: string, name: string, data: Record<string, unknown>): Promise<void>;
87
92
  private callProtocolV1;
88
93
  private callProtocolV2;
89
94
  private createProtocolV2Adapter;
@@ -1 +1 @@
1
- {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as _onekeyfe_hd_transport from '@onekeyfe/hd-transport';
2
2
  import _onekeyfe_hd_transport__default, { ProtocolV2UsbTransportBase, AcquireInput, TransportCallOptions, ProtocolV2Schemas, ProtocolV2CallContext, ProtocolType, OneKeyDeviceInfoBase, OneKeyDeviceInfo } from '@onekeyfe/hd-transport';
3
+ import EventEmitter from 'events';
3
4
  import { Deferred } from '@onekeyfe/hd-shared';
4
5
  import { DesktopAPI } from '@onekeyfe/hd-transport-electron';
5
- import EventEmitter from 'events';
6
6
 
7
7
  /**
8
8
  * Device information with path and WebUSB device instance
@@ -19,7 +19,24 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
19
19
  private protocolV2SchemaSource;
20
20
  /** Per-path protocol type detected by active wire-level probe. */
21
21
  private deviceProtocol;
22
+ /** Protocols previously confirmed by an active response for this transport instance. */
23
+ private confirmedDeviceProtocols;
22
24
  private deviceProtocolHints;
25
+ /**
26
+ * Paths whose cached protocol must be re-probed on the next acquire (a USB
27
+ * disconnect was seen, or a transfer-level reconnect happened mid-call).
28
+ */
29
+ private staleProtocolPaths;
30
+ /** Paths currently acquired (between a successful acquire() and its release()). */
31
+ private acquiredPaths;
32
+ /**
33
+ * The exact USBDevice object each cached protocol was probed against. The
34
+ * browser returns the same object identity for a device as long as it stays
35
+ * connected, and a replug/reboot always yields a new object — so an identity
36
+ * mismatch proves the device was re-enumerated since the probe, even when the
37
+ * disconnect event itself was delayed or missed.
38
+ */
39
+ private probedDeviceObjects;
23
40
  /** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
24
41
  private deviceEndpoints;
25
42
  name: string;
@@ -27,6 +44,7 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
27
44
  configured: boolean;
28
45
  Log?: any;
29
46
  usb?: USB;
47
+ emitter?: EventEmitter;
30
48
  /**
31
49
  * Cached list of connected devices
32
50
  * This is essential for maintaining device references between operations
@@ -39,7 +57,23 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
39
57
  /**
40
58
  * Initialize WebUSB transport
41
59
  */
42
- init(logger: any): void;
60
+ init(logger: any, emitter?: EventEmitter): void;
61
+ /**
62
+ * Announce that a USB device left. Called from the module-scoped disconnect
63
+ * listener, which is why it is public rather than inlined.
64
+ */
65
+ emitDeviceDisconnect(path: string, device?: USBDevice): void;
66
+ /**
67
+ * Protocol type is a property of the physical device keyed by USB serial number.
68
+ * It can only change across a device reboot (e.g. normal ↔ bootloader mode), and
69
+ * a reboot always surfaces as a USB disconnect. Disconnects (and transfer-level
70
+ * reconnects, which cover a missed disconnect event) only MARK the cached probe
71
+ * result stale instead of deleting it: an in-flight session keeps using the old
72
+ * value so the transfer-level reconnect retries can absorb a transient
73
+ * re-enumeration exactly as they did before the cache existed, while the next
74
+ * acquire re-probes from scratch.
75
+ */
76
+ markProtocolStale(path: string): void;
43
77
  /**
44
78
  * Configure Protocol V1 protobuf schema (legacy chunked 0x3F framing).
45
79
  */
@@ -97,6 +131,14 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
97
131
  connectToDevice(path: string, first: boolean): Promise<void>;
98
132
  private closeOpenDevice;
99
133
  private clearEndpointHalt;
134
+ /**
135
+ * With the protocol cache surviving release(), deviceProtocol presence no
136
+ * longer implies an active session. Guard call()/post() explicitly so a
137
+ * post-release straggler fails fast instead of silently reopening the device
138
+ * and driving it outside any session (pre-cache behavior: the deleted
139
+ * protocol entry produced the same fail-fast).
140
+ */
141
+ private assertAcquired;
100
142
  post(session: string, name: string, data: Record<string, unknown>): Promise<void>;
101
143
  private getErrorMessage;
102
144
  private isRetryablePacketIoError;
@@ -176,6 +218,8 @@ declare class ElectronBleTransport {
176
218
  private connectedDevices;
177
219
  private deviceProtocol;
178
220
  private deviceProtocolHints;
221
+ /** Endpoints that answered a V2 probe in this transport lifetime. Survives disconnect. */
222
+ private confirmedProtocolV2;
179
223
  private deviceMtus;
180
224
  private devicePacketCapacities;
181
225
  private v1Buffers;
@@ -187,12 +231,27 @@ declare class ElectronBleTransport {
187
231
  private warnedMissingRelease;
188
232
  private notificationCleanups;
189
233
  private mtuCleanups;
190
- private disconnectCleanups;
234
+ /**
235
+ * Transport-lifetime subscription to host BLE disconnects.
236
+ *
237
+ * This must NOT be scoped to acquire()/release(): a logical release keeps the
238
+ * native link alive for the keep-alive window, so a device that drops while
239
+ * idle would otherwise go unobserved and consumers would never learn it left
240
+ * (OK-60486).
241
+ */
242
+ private hostDisconnectCleanup?;
191
243
  private notificationTokens;
192
244
  private nextNotificationToken;
245
+ private toStaleBondError;
193
246
  private handleBluetoothError;
194
247
  private cleanupDeviceState;
195
248
  init(logger: any, emitter?: EventEmitter): void;
249
+ /**
250
+ * One host subscription for the whole transport lifetime. init() can run
251
+ * again after an SDK reset, so drop the previous listener first rather than
252
+ * stacking duplicates.
253
+ */
254
+ private subscribeHostDisconnects;
196
255
  configure(signedData: any): void;
197
256
  configureProtocolV2(signedData: any): void;
198
257
  listen(): Promise<OneKeyDeviceInfo[]>;
@@ -226,6 +285,7 @@ declare class ElectronBleTransport {
226
285
  private createMtuSubscription;
227
286
  private writeProtocolV2Frame;
228
287
  private handleNotification;
288
+ private readProtocolV2LinkDisabledFailure;
229
289
  private handleProtocolV2Notification;
230
290
  private getProtocolV2FrameQueue;
231
291
  private resolveProtocolV2Frame;
@@ -234,6 +294,7 @@ declare class ElectronBleTransport {
234
294
  private readProtocolV2Frame;
235
295
  private handleProtocolV1Notification;
236
296
  call(uuid: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<_onekeyfe_hd_transport.MessageFromOneKey>;
297
+ post(uuid: string, name: string, data: Record<string, unknown>): Promise<void>;
237
298
  private callProtocolV1;
238
299
  private callProtocolV2;
239
300
  private createProtocolV2Adapter;