@onekeyfe/hd-transport-web-device 1.2.0-alpha.13 → 1.2.0-alpha.131
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 +377 -22
- package/__tests__/webusb-protocol-v2-timeout.test.ts +357 -11
- package/dist/ble-packet-capacity.d.ts +2 -0
- package/dist/ble-packet-capacity.d.ts.map +1 -0
- package/dist/electron-ble-transport.d.ts +16 -3
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +110 -12
- package/dist/index.js +345 -255
- package/dist/transportLog.d.ts +1 -6
- package/dist/transportLog.d.ts.map +1 -1
- package/dist/webusb.d.ts +15 -10
- package/dist/webusb.d.ts.map +1 -1
- package/jest.config.js +5 -0
- package/package.json +6 -5
- package/src/ble-packet-capacity.ts +13 -0
- package/src/electron-ble-transport.ts +241 -104
- package/src/transportLog.ts +1 -11
- package/src/webusb.ts +173 -189
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import transport, { PROTOCOL_V2_CHANNEL_BLE_UART, bytesToHex } from '@onekeyfe/hd-transport';
|
|
2
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
3
|
+
import EventEmitter from 'events';
|
|
3
4
|
|
|
4
5
|
import ElectronBleTransport from '../src/electron-ble-transport';
|
|
5
6
|
|
|
@@ -10,6 +11,9 @@ const protocolV1Schema = {
|
|
|
10
11
|
Initialize: {
|
|
11
12
|
fields: {},
|
|
12
13
|
},
|
|
14
|
+
GetFeatures: {
|
|
15
|
+
fields: {},
|
|
16
|
+
},
|
|
13
17
|
Success: {
|
|
14
18
|
fields: {
|
|
15
19
|
message: {
|
|
@@ -22,6 +26,7 @@ const protocolV1Schema = {
|
|
|
22
26
|
values: {
|
|
23
27
|
MessageType_Initialize: 1,
|
|
24
28
|
MessageType_Success: 2,
|
|
29
|
+
MessageType_GetFeatures: 55,
|
|
25
30
|
},
|
|
26
31
|
},
|
|
27
32
|
},
|
|
@@ -100,6 +105,7 @@ const createNobleBle = (device = { id: 'flaky-pro2-id', name: 'Unknown BLE Devic
|
|
|
100
105
|
unsubscribe: jest.fn(() => Promise.resolve()),
|
|
101
106
|
write: jest.fn(() => Promise.resolve()),
|
|
102
107
|
onNotification: jest.fn(() => jest.fn()),
|
|
108
|
+
onMtuChanged: jest.fn(() => jest.fn()),
|
|
103
109
|
onDeviceDisconnected: jest.fn(() => jest.fn()),
|
|
104
110
|
checkAvailability: jest.fn(() =>
|
|
105
111
|
Promise.resolve({
|
|
@@ -111,7 +117,10 @@ const createNobleBle = (device = { id: 'flaky-pro2-id', name: 'Unknown BLE Devic
|
|
|
111
117
|
),
|
|
112
118
|
});
|
|
113
119
|
|
|
114
|
-
const configureTransport = (
|
|
120
|
+
const configureTransport = (
|
|
121
|
+
nobleBle: ReturnType<typeof createNobleBle>,
|
|
122
|
+
emitter?: EventEmitter
|
|
123
|
+
) => {
|
|
115
124
|
(global as any).window = {
|
|
116
125
|
desktopApi: {
|
|
117
126
|
nobleBle,
|
|
@@ -119,7 +128,7 @@ const configureTransport = (nobleBle: ReturnType<typeof createNobleBle>) => {
|
|
|
119
128
|
};
|
|
120
129
|
|
|
121
130
|
const transport = new ElectronBleTransport();
|
|
122
|
-
transport.init(createLogger());
|
|
131
|
+
transport.init(createLogger(), emitter);
|
|
123
132
|
transport.configure(protocolV1Schema);
|
|
124
133
|
transport.configureProtocolV2(protocolV2Schema);
|
|
125
134
|
return transport;
|
|
@@ -131,6 +140,128 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
131
140
|
jest.clearAllMocks();
|
|
132
141
|
});
|
|
133
142
|
|
|
143
|
+
test('keeps raw BLE lifecycle payloads off the public device event channel', async () => {
|
|
144
|
+
const device = { id: 'lifecycle-pro2-id', name: 'OneKey Pro 2' };
|
|
145
|
+
const nobleBle = createNobleBle(device);
|
|
146
|
+
const emitter = new EventEmitter();
|
|
147
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
148
|
+
let disconnectHandler: ((device: { id: string; name: string | null }) => void) | undefined;
|
|
149
|
+
let responseSeq = 0;
|
|
150
|
+
|
|
151
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
152
|
+
notificationHandler = handler;
|
|
153
|
+
return jest.fn();
|
|
154
|
+
});
|
|
155
|
+
nobleBle.onDeviceDisconnected.mockImplementation(handler => {
|
|
156
|
+
disconnectHandler = handler;
|
|
157
|
+
return jest.fn();
|
|
158
|
+
});
|
|
159
|
+
nobleBle.write.mockImplementation(() => {
|
|
160
|
+
responseSeq += 1;
|
|
161
|
+
const response = ProtocolV2.encodeFrame(
|
|
162
|
+
schemas,
|
|
163
|
+
'Success',
|
|
164
|
+
{ message: 'ok' },
|
|
165
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
166
|
+
);
|
|
167
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(response)), 0);
|
|
168
|
+
return Promise.resolve();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const publicConnect = jest.fn();
|
|
172
|
+
const publicDisconnect = jest.fn();
|
|
173
|
+
const transportDisconnect = jest.fn();
|
|
174
|
+
emitter.on('device-connect', publicConnect);
|
|
175
|
+
emitter.on('device-disconnect', publicDisconnect);
|
|
176
|
+
emitter.on('transport-device-disconnect', transportDisconnect);
|
|
177
|
+
const bleTransport = configureTransport(nobleBle, emitter);
|
|
178
|
+
|
|
179
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
180
|
+
expect(nobleBle.subscribe.mock.invocationCallOrder[0]).toBeLessThan(
|
|
181
|
+
nobleBle.getDevice.mock.invocationCallOrder[1]
|
|
182
|
+
);
|
|
183
|
+
disconnectHandler?.(device);
|
|
184
|
+
|
|
185
|
+
expect(publicConnect).not.toHaveBeenCalled();
|
|
186
|
+
expect(publicDisconnect).not.toHaveBeenCalled();
|
|
187
|
+
expect(transportDisconnect).toHaveBeenCalledWith({
|
|
188
|
+
id: device.id,
|
|
189
|
+
connectId: device.id,
|
|
190
|
+
name: device.name,
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('uses the Protocol V2 BLE writer with the Electron packet size', async () => {
|
|
195
|
+
const device = { id: 'chunked-pro2-id', name: 'OneKey Pro 2' };
|
|
196
|
+
const nobleBle = createNobleBle(device);
|
|
197
|
+
const bleTransport = configureTransport(nobleBle) as any;
|
|
198
|
+
const context = {
|
|
199
|
+
messageName: 'Ping',
|
|
200
|
+
timeoutMs: 1000,
|
|
201
|
+
highThroughput: false,
|
|
202
|
+
generation: 1,
|
|
203
|
+
signal: new AbortController().signal,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
await bleTransport.writeProtocolV2Frame(device.id, new Uint8Array(193), context, jest.fn());
|
|
207
|
+
|
|
208
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(2);
|
|
209
|
+
expect(nobleBle.write.mock.calls.map(([, hex]) => hex.length / 2)).toEqual([192, 1]);
|
|
210
|
+
expect(nobleBle.write.mock.calls.every(([, , options]) => options?.pacingDelayMs === 0)).toBe(
|
|
211
|
+
true
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('uses the negotiated Noble MTU for Protocol V2 BLE writes', async () => {
|
|
216
|
+
const device = { id: 'mtu-pro2-id', name: 'OneKey Pro 2', mtu: 247 };
|
|
217
|
+
const nobleBle = createNobleBle(device);
|
|
218
|
+
const bleTransport = configureTransport(nobleBle) as any;
|
|
219
|
+
const context = {
|
|
220
|
+
messageName: 'FilesystemFileWrite',
|
|
221
|
+
timeoutMs: 1000,
|
|
222
|
+
highThroughput: true,
|
|
223
|
+
generation: 1,
|
|
224
|
+
signal: new AbortController().signal,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
|
|
228
|
+
try {
|
|
229
|
+
await bleTransport.refreshBlePacketCapacity(device.id);
|
|
230
|
+
await bleTransport.writeProtocolV2Frame(device.id, new Uint8Array(245), context, jest.fn());
|
|
231
|
+
|
|
232
|
+
expect(setTimeoutSpy).not.toHaveBeenCalled();
|
|
233
|
+
} finally {
|
|
234
|
+
setTimeoutSpy.mockRestore();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(2);
|
|
238
|
+
expect(nobleBle.write.mock.calls.map(([, hex]) => hex.length / 2)).toEqual([244, 1]);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('updates Protocol V2 packet capacity when Noble reports a new MTU', async () => {
|
|
242
|
+
const device = { id: 'mtu-event-pro2-id', name: 'OneKey Pro 2' };
|
|
243
|
+
const nobleBle = createNobleBle(device);
|
|
244
|
+
let mtuHandler: ((changedDevice: { id: string; mtu: number }) => void) | undefined;
|
|
245
|
+
nobleBle.onMtuChanged.mockImplementation(handler => {
|
|
246
|
+
mtuHandler = handler;
|
|
247
|
+
return jest.fn();
|
|
248
|
+
});
|
|
249
|
+
const bleTransport = configureTransport(nobleBle) as any;
|
|
250
|
+
const context = {
|
|
251
|
+
messageName: 'FilesystemFileWrite',
|
|
252
|
+
timeoutMs: 1000,
|
|
253
|
+
highThroughput: true,
|
|
254
|
+
generation: 1,
|
|
255
|
+
signal: new AbortController().signal,
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
bleTransport.createMtuSubscription(device.id);
|
|
259
|
+
mtuHandler?.({ id: device.id, mtu: 247 });
|
|
260
|
+
await bleTransport.writeProtocolV2Frame(device.id, new Uint8Array(245), context, jest.fn());
|
|
261
|
+
|
|
262
|
+
expect(nobleBle.write.mock.calls.map(([, hex]) => hex.length / 2)).toEqual([244, 1]);
|
|
263
|
+
});
|
|
264
|
+
|
|
134
265
|
test('detects Protocol V2 after Protocol V1 probe timeout', async () => {
|
|
135
266
|
const device = { id: 'unknown-pro2-id', name: 'Unknown BLE Device' };
|
|
136
267
|
const nobleBle = createNobleBle(device);
|
|
@@ -141,7 +272,6 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
141
272
|
{ message: 'ok' },
|
|
142
273
|
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
143
274
|
);
|
|
144
|
-
|
|
145
275
|
nobleBle.onNotification.mockImplementation(handler => {
|
|
146
276
|
notificationHandler = handler;
|
|
147
277
|
return jest.fn();
|
|
@@ -164,12 +294,16 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
164
294
|
})
|
|
165
295
|
);
|
|
166
296
|
expect(transport.getProtocolType(device.id)).toBe('V2');
|
|
297
|
+
expect(nobleBle.connect).toHaveBeenCalledTimes(1);
|
|
298
|
+
expect(nobleBle.subscribe).toHaveBeenCalledTimes(1);
|
|
299
|
+
expect(nobleBle.unsubscribe).not.toHaveBeenCalled();
|
|
300
|
+
expect(nobleBle.disconnect).not.toHaveBeenCalled();
|
|
167
301
|
} finally {
|
|
168
302
|
await transport.release(device.id);
|
|
169
303
|
}
|
|
170
304
|
});
|
|
171
305
|
|
|
172
|
-
test('
|
|
306
|
+
test('reconnects Protocol V1 with a non-destructive GetFeatures probe', async () => {
|
|
173
307
|
const device = { id: 'classic-id', name: 'OneKey Classic' };
|
|
174
308
|
const nobleBle = createNobleBle(device);
|
|
175
309
|
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
@@ -183,11 +317,12 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
183
317
|
return jest.fn();
|
|
184
318
|
});
|
|
185
319
|
nobleBle.write.mockImplementation(() => {
|
|
186
|
-
//
|
|
320
|
+
// The first write is the V1 GetFeatures probe; answer with a V1 Success response.
|
|
187
321
|
setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
|
|
188
322
|
return Promise.resolve();
|
|
189
323
|
});
|
|
190
324
|
const transport = configureTransport(nobleBle);
|
|
325
|
+
const protocolV2Writer = jest.spyOn(transport as any, 'writeProtocolV2Frame');
|
|
191
326
|
|
|
192
327
|
try {
|
|
193
328
|
await expect(transport.acquire({ uuid: device.id })).resolves.toEqual(
|
|
@@ -196,11 +331,100 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
196
331
|
})
|
|
197
332
|
);
|
|
198
333
|
expect(transport.getProtocolType(device.id)).toBe('V1');
|
|
334
|
+
await expect(transport.acquire({ uuid: device.id, expectedProtocol: 'V1' })).resolves.toEqual(
|
|
335
|
+
expect.objectContaining({
|
|
336
|
+
uuid: device.id,
|
|
337
|
+
})
|
|
338
|
+
);
|
|
339
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(2);
|
|
340
|
+
expect(nobleBle.write.mock.calls.every(([, hex]) => /^3f23230037/.test(hex))).toBe(true);
|
|
341
|
+
expect(protocolV2Writer).not.toHaveBeenCalled();
|
|
199
342
|
} finally {
|
|
200
343
|
await transport.release(device.id);
|
|
201
344
|
}
|
|
202
345
|
});
|
|
203
346
|
|
|
347
|
+
test('invalidates and disconnects a Protocol V1 link after a response timeout', async () => {
|
|
348
|
+
const device = { id: 'classic-timeout-id', name: 'OneKey Classic' };
|
|
349
|
+
const nobleBle = createNobleBle(device);
|
|
350
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
351
|
+
const v1ResponseHex = '3f23230002000000040a026f6b';
|
|
352
|
+
let writeCount = 0;
|
|
353
|
+
|
|
354
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
355
|
+
notificationHandler = handler;
|
|
356
|
+
return jest.fn();
|
|
357
|
+
});
|
|
358
|
+
nobleBle.write.mockImplementation(() => {
|
|
359
|
+
writeCount += 1;
|
|
360
|
+
if (writeCount === 1) {
|
|
361
|
+
setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
|
|
362
|
+
}
|
|
363
|
+
return Promise.resolve();
|
|
364
|
+
});
|
|
365
|
+
const bleTransport = configureTransport(nobleBle);
|
|
366
|
+
|
|
367
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V1' });
|
|
368
|
+
await expect(
|
|
369
|
+
bleTransport.call(device.id, 'Initialize', {}, { timeoutMs: 5 })
|
|
370
|
+
).rejects.toMatchObject({ errorCode: HardwareErrorCode.BleTimeoutError });
|
|
371
|
+
|
|
372
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
373
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
374
|
+
expect(bleTransport.getProtocolType(device.id)).toBeUndefined();
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test('keeps another device V2 reader when force-cleaning a V1 call', async () => {
|
|
378
|
+
const device = { id: 'classic-force-clean-id', name: 'OneKey Classic' };
|
|
379
|
+
const nobleBle = createNobleBle(device);
|
|
380
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
381
|
+
const v1ResponseHex = '3f23230002000000040a026f6b';
|
|
382
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
383
|
+
notificationHandler = handler;
|
|
384
|
+
return jest.fn();
|
|
385
|
+
});
|
|
386
|
+
nobleBle.write.mockImplementation(() => {
|
|
387
|
+
setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
|
|
388
|
+
return Promise.resolve();
|
|
389
|
+
});
|
|
390
|
+
const bleTransport = configureTransport(nobleBle) as any;
|
|
391
|
+
const activeV1Call = createDeferred<string>();
|
|
392
|
+
const otherDeviceReader = createDeferred<Uint8Array>();
|
|
393
|
+
activeV1Call.promise.catch(() => undefined);
|
|
394
|
+
otherDeviceReader.promise.catch(() => undefined);
|
|
395
|
+
bleTransport.runPromise = activeV1Call;
|
|
396
|
+
bleTransport.v2FramePromises.set('device-b', otherDeviceReader);
|
|
397
|
+
|
|
398
|
+
await bleTransport.acquire({
|
|
399
|
+
uuid: device.id,
|
|
400
|
+
expectedProtocol: 'V1',
|
|
401
|
+
forceCleanRunPromise: true,
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
expect(bleTransport.v2FramePromises.get('device-b')).toBe(otherDeviceReader);
|
|
405
|
+
await bleTransport.release(device.id);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test('rejects a pending V2 reader when its device frame state resets', async () => {
|
|
409
|
+
const nobleBle = createNobleBle();
|
|
410
|
+
const bleTransport = configureTransport(nobleBle) as any;
|
|
411
|
+
const reader = createDeferred<Uint8Array>();
|
|
412
|
+
bleTransport.v2FramePromises.set('device-a', reader);
|
|
413
|
+
const result = Promise.race([
|
|
414
|
+
reader.promise.then(
|
|
415
|
+
() => 'resolved',
|
|
416
|
+
() => 'rejected'
|
|
417
|
+
),
|
|
418
|
+
new Promise(resolve => {
|
|
419
|
+
setTimeout(() => resolve('pending'), 20);
|
|
420
|
+
}),
|
|
421
|
+
]);
|
|
422
|
+
|
|
423
|
+
bleTransport.resetProtocolV2Frames('device-a');
|
|
424
|
+
|
|
425
|
+
await expect(result).resolves.toBe('rejected');
|
|
426
|
+
});
|
|
427
|
+
|
|
204
428
|
test('throws when both protocol probes fail', async () => {
|
|
205
429
|
const device = { id: 'dead-device-id', name: 'Unknown Device' };
|
|
206
430
|
const nobleBle = createNobleBle(device);
|
|
@@ -220,19 +444,21 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
220
444
|
const device = { id: 'named-pro2-id', name: 'OneKey Pro 2' };
|
|
221
445
|
const nobleBle = createNobleBle(device);
|
|
222
446
|
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
223
|
-
const probeResponse = ProtocolV2.encodeFrame(
|
|
224
|
-
schemas,
|
|
225
|
-
'Success',
|
|
226
|
-
{ message: 'ok' },
|
|
227
|
-
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
228
|
-
);
|
|
229
447
|
|
|
230
448
|
nobleBle.onNotification.mockImplementation(handler => {
|
|
231
449
|
notificationHandler = handler;
|
|
232
450
|
return jest.fn();
|
|
233
451
|
});
|
|
452
|
+
let responseSeq = 0;
|
|
234
453
|
nobleBle.write.mockImplementation(() => {
|
|
235
|
-
|
|
454
|
+
responseSeq += 1;
|
|
455
|
+
const response = ProtocolV2.encodeFrame(
|
|
456
|
+
schemas,
|
|
457
|
+
'Success',
|
|
458
|
+
{ message: 'ok' },
|
|
459
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
460
|
+
);
|
|
461
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(response)), 0);
|
|
236
462
|
return Promise.resolve();
|
|
237
463
|
});
|
|
238
464
|
const transport = configureTransport(nobleBle);
|
|
@@ -304,19 +530,20 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
304
530
|
const device = { id: 'repeated-acquire-pro2-id', name: 'OneKey Pro 2' };
|
|
305
531
|
const nobleBle = createNobleBle(device);
|
|
306
532
|
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
307
|
-
const response = ProtocolV2.encodeFrame(
|
|
308
|
-
schemas,
|
|
309
|
-
'Success',
|
|
310
|
-
{ message: 'ok' },
|
|
311
|
-
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
312
|
-
);
|
|
313
|
-
|
|
314
533
|
nobleBle.onNotification.mockImplementation(handler => {
|
|
315
534
|
notificationHandler = handler;
|
|
316
535
|
return jest.fn();
|
|
317
536
|
});
|
|
537
|
+
let responseSeq = 0;
|
|
318
538
|
nobleBle.write.mockImplementation(() => {
|
|
319
|
-
|
|
539
|
+
responseSeq += 1;
|
|
540
|
+
const sequencedResponse = ProtocolV2.encodeFrame(
|
|
541
|
+
schemas,
|
|
542
|
+
'Success',
|
|
543
|
+
{ message: 'ok' },
|
|
544
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
545
|
+
);
|
|
546
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(sequencedResponse)), 0);
|
|
320
547
|
return Promise.resolve();
|
|
321
548
|
});
|
|
322
549
|
const transport = configureTransport(nobleBle);
|
|
@@ -334,9 +561,137 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
334
561
|
const sentSeqs = nobleBle.write.mock.calls.map(([, hex]) =>
|
|
335
562
|
Number.parseInt(hex.slice(12, 14), 16)
|
|
336
563
|
);
|
|
337
|
-
expect(sentSeqs).toEqual([1, 2]);
|
|
564
|
+
expect(sentSeqs).toEqual([1, 2, 3]);
|
|
338
565
|
} finally {
|
|
339
566
|
await transport.release(device.id);
|
|
340
567
|
}
|
|
341
568
|
});
|
|
569
|
+
|
|
570
|
+
test('ignores a delayed disconnect event from the previous BLE connection', async () => {
|
|
571
|
+
const device = { id: 'delayed-disconnect-pro2-id', name: 'OneKey Pro 2' };
|
|
572
|
+
const nobleBle = createNobleBle(device);
|
|
573
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
574
|
+
const disconnectHandlers: Array<
|
|
575
|
+
(disconnectedDevice: { id: string; name: string | null }) => void
|
|
576
|
+
> = [];
|
|
577
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
578
|
+
notificationHandler = handler;
|
|
579
|
+
return jest.fn();
|
|
580
|
+
});
|
|
581
|
+
nobleBle.onDeviceDisconnected.mockImplementation(handler => {
|
|
582
|
+
disconnectHandlers.push(handler);
|
|
583
|
+
return jest.fn();
|
|
584
|
+
});
|
|
585
|
+
let responseSeq = 0;
|
|
586
|
+
nobleBle.write.mockImplementation(() => {
|
|
587
|
+
responseSeq += 1;
|
|
588
|
+
const response = ProtocolV2.encodeFrame(
|
|
589
|
+
schemas,
|
|
590
|
+
'Success',
|
|
591
|
+
{ message: 'ok' },
|
|
592
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
593
|
+
);
|
|
594
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(response)), 0);
|
|
595
|
+
return Promise.resolve();
|
|
596
|
+
});
|
|
597
|
+
const bleTransport = configureTransport(nobleBle);
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
601
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
602
|
+
|
|
603
|
+
disconnectHandlers[0]?.(device);
|
|
604
|
+
|
|
605
|
+
expect(bleTransport.getProtocolType(device.id)).toBe('V2');
|
|
606
|
+
await expect(
|
|
607
|
+
bleTransport.call(device.id, 'Ping', { message: 'after-stale-disconnect' })
|
|
608
|
+
).resolves.toEqual({
|
|
609
|
+
type: 'Success',
|
|
610
|
+
message: { message: 'ok' },
|
|
611
|
+
});
|
|
612
|
+
} finally {
|
|
613
|
+
await bleTransport.release(device.id);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
test('preserves the active Protocol V2 link when the same schema is configured again', async () => {
|
|
618
|
+
const device = { id: 'stable-schema-pro2-id', name: 'OneKey Pro 2' };
|
|
619
|
+
const nobleBle = createNobleBle(device);
|
|
620
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
621
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
622
|
+
notificationHandler = handler;
|
|
623
|
+
return jest.fn();
|
|
624
|
+
});
|
|
625
|
+
let responseSeq = 0;
|
|
626
|
+
nobleBle.write.mockImplementation(() => {
|
|
627
|
+
responseSeq += 1;
|
|
628
|
+
const response = ProtocolV2.encodeFrame(
|
|
629
|
+
schemas,
|
|
630
|
+
'Success',
|
|
631
|
+
{ message: 'ok' },
|
|
632
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
633
|
+
);
|
|
634
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(response)), 0);
|
|
635
|
+
return Promise.resolve();
|
|
636
|
+
});
|
|
637
|
+
const bleTransport = configureTransport(nobleBle);
|
|
638
|
+
|
|
639
|
+
try {
|
|
640
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
641
|
+
const invalidateAllLinks = jest.spyOn(
|
|
642
|
+
(bleTransport as any).protocolV2Links,
|
|
643
|
+
'invalidateAllLinks'
|
|
644
|
+
);
|
|
645
|
+
bleTransport.configureProtocolV2(protocolV2Schema);
|
|
646
|
+
await new Promise<void>(resolve => {
|
|
647
|
+
setTimeout(resolve, 0);
|
|
648
|
+
});
|
|
649
|
+
expect(invalidateAllLinks).not.toHaveBeenCalled();
|
|
650
|
+
await expect(
|
|
651
|
+
bleTransport.call(device.id, 'Ping', { message: 'same-schema' })
|
|
652
|
+
).resolves.toEqual({
|
|
653
|
+
type: 'Success',
|
|
654
|
+
message: { message: 'ok' },
|
|
655
|
+
});
|
|
656
|
+
const sentSeqs = nobleBle.write.mock.calls.map(([, hex]) =>
|
|
657
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
658
|
+
);
|
|
659
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
660
|
+
} finally {
|
|
661
|
+
await bleTransport.release(device.id);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
test('rejects oversized Protocol V2 requests before writing to Electron BLE', async () => {
|
|
666
|
+
const device = { id: 'oversized-frame-pro2-id', name: 'OneKey Pro 2' };
|
|
667
|
+
const nobleBle = createNobleBle(device);
|
|
668
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
669
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
670
|
+
schemas,
|
|
671
|
+
'Success',
|
|
672
|
+
{ message: 'ok' },
|
|
673
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
674
|
+
);
|
|
675
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
676
|
+
notificationHandler = handler;
|
|
677
|
+
return jest.fn();
|
|
678
|
+
});
|
|
679
|
+
nobleBle.write.mockImplementation(() => {
|
|
680
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(probeResponse)), 0);
|
|
681
|
+
return Promise.resolve();
|
|
682
|
+
});
|
|
683
|
+
const bleTransport = configureTransport(nobleBle);
|
|
684
|
+
|
|
685
|
+
try {
|
|
686
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
687
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(1);
|
|
688
|
+
|
|
689
|
+
await expect(
|
|
690
|
+
bleTransport.call(device.id, 'Ping', { message: 'x'.repeat(2048) })
|
|
691
|
+
).rejects.toThrow(/Protocol V2 frame too large for transport/);
|
|
692
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(1);
|
|
693
|
+
} finally {
|
|
694
|
+
await bleTransport.release(device.id);
|
|
695
|
+
}
|
|
696
|
+
});
|
|
342
697
|
});
|