@onekeyfe/hd-transport-web-device 1.2.0-alpha.99 → 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.
- package/__tests__/electron-ble-transport.test.ts +289 -32
- package/__tests__/webusb-protocol-cache.test.ts +276 -0
- package/__tests__/webusb-protocol-v2-timeout.test.ts +22 -8
- package/dist/electron-ble-transport.d.ts +6 -1
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +64 -3
- package/dist/index.js +232 -110
- package/dist/webusb.d.ts +11 -1
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/electron-ble-transport.ts +172 -85
- package/src/webusb.ts +201 -33
|
@@ -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('
|
|
25
|
-
const filter =
|
|
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
|
|
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
|
|
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.
|
|
160
|
-
|
|
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
|
-
|
|
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
|
|
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":";
|
|
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
|
-
|
|
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;
|