@onekeyfe/hd-transport-web-device 1.2.0-alpha.7 → 1.2.0-alpha.70
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 +396 -12
- package/__tests__/webusb-protocol-v2-timeout.test.ts +385 -0
- package/dist/electron-ble-transport.d.ts +8 -5
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +101 -14
- package/dist/index.js +320 -321
- package/dist/transportLog.d.ts +2 -0
- package/dist/transportLog.d.ts.map +1 -0
- 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/electron-ble-transport.ts +218 -175
- package/src/transportLog.ts +1 -0
- package/src/webusb.ts +184 -217
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import transport, { PROTOCOL_V2_CHANNEL_BLE_UART, bytesToHex } from '@onekeyfe/hd-transport';
|
|
2
|
+
import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
3
|
+
import EventEmitter from 'events';
|
|
2
4
|
|
|
3
5
|
import ElectronBleTransport from '../src/electron-ble-transport';
|
|
4
6
|
|
|
@@ -9,6 +11,9 @@ const protocolV1Schema = {
|
|
|
9
11
|
Initialize: {
|
|
10
12
|
fields: {},
|
|
11
13
|
},
|
|
14
|
+
GetFeatures: {
|
|
15
|
+
fields: {},
|
|
16
|
+
},
|
|
12
17
|
Success: {
|
|
13
18
|
fields: {
|
|
14
19
|
message: {
|
|
@@ -21,6 +26,7 @@ const protocolV1Schema = {
|
|
|
21
26
|
values: {
|
|
22
27
|
MessageType_Initialize: 1,
|
|
23
28
|
MessageType_Success: 2,
|
|
29
|
+
MessageType_GetFeatures: 55,
|
|
24
30
|
},
|
|
25
31
|
},
|
|
26
32
|
},
|
|
@@ -110,7 +116,10 @@ const createNobleBle = (device = { id: 'flaky-pro2-id', name: 'Unknown BLE Devic
|
|
|
110
116
|
),
|
|
111
117
|
});
|
|
112
118
|
|
|
113
|
-
const configureTransport = (
|
|
119
|
+
const configureTransport = (
|
|
120
|
+
nobleBle: ReturnType<typeof createNobleBle>,
|
|
121
|
+
emitter?: EventEmitter
|
|
122
|
+
) => {
|
|
114
123
|
(global as any).window = {
|
|
115
124
|
desktopApi: {
|
|
116
125
|
nobleBle,
|
|
@@ -118,7 +127,7 @@ const configureTransport = (nobleBle: ReturnType<typeof createNobleBle>) => {
|
|
|
118
127
|
};
|
|
119
128
|
|
|
120
129
|
const transport = new ElectronBleTransport();
|
|
121
|
-
transport.init(createLogger());
|
|
130
|
+
transport.init(createLogger(), emitter);
|
|
122
131
|
transport.configure(protocolV1Schema);
|
|
123
132
|
transport.configureProtocolV2(protocolV2Schema);
|
|
124
133
|
return transport;
|
|
@@ -130,6 +139,72 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
130
139
|
jest.clearAllMocks();
|
|
131
140
|
});
|
|
132
141
|
|
|
142
|
+
test('keeps raw BLE lifecycle payloads off the public device event channel', async () => {
|
|
143
|
+
const device = { id: 'lifecycle-pro2-id', name: 'OneKey Pro 2' };
|
|
144
|
+
const nobleBle = createNobleBle(device);
|
|
145
|
+
const emitter = new EventEmitter();
|
|
146
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
147
|
+
let disconnectHandler: ((device: { id: string; name: string | null }) => void) | undefined;
|
|
148
|
+
let responseSeq = 0;
|
|
149
|
+
|
|
150
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
151
|
+
notificationHandler = handler;
|
|
152
|
+
return jest.fn();
|
|
153
|
+
});
|
|
154
|
+
nobleBle.onDeviceDisconnected.mockImplementation(handler => {
|
|
155
|
+
disconnectHandler = handler;
|
|
156
|
+
return jest.fn();
|
|
157
|
+
});
|
|
158
|
+
nobleBle.write.mockImplementation(() => {
|
|
159
|
+
responseSeq += 1;
|
|
160
|
+
const response = ProtocolV2.encodeFrame(
|
|
161
|
+
schemas,
|
|
162
|
+
'Success',
|
|
163
|
+
{ message: 'ok' },
|
|
164
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
165
|
+
);
|
|
166
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(response)), 0);
|
|
167
|
+
return Promise.resolve();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const publicConnect = jest.fn();
|
|
171
|
+
const publicDisconnect = jest.fn();
|
|
172
|
+
const transportDisconnect = jest.fn();
|
|
173
|
+
emitter.on('device-connect', publicConnect);
|
|
174
|
+
emitter.on('device-disconnect', publicDisconnect);
|
|
175
|
+
emitter.on('transport-device-disconnect', transportDisconnect);
|
|
176
|
+
const bleTransport = configureTransport(nobleBle, emitter);
|
|
177
|
+
|
|
178
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
179
|
+
disconnectHandler?.(device);
|
|
180
|
+
|
|
181
|
+
expect(publicConnect).not.toHaveBeenCalled();
|
|
182
|
+
expect(publicDisconnect).not.toHaveBeenCalled();
|
|
183
|
+
expect(transportDisconnect).toHaveBeenCalledWith({
|
|
184
|
+
id: device.id,
|
|
185
|
+
connectId: device.id,
|
|
186
|
+
name: device.name,
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('uses the Protocol V2 BLE writer with the Electron packet size', async () => {
|
|
191
|
+
const device = { id: 'chunked-pro2-id', name: 'OneKey Pro 2' };
|
|
192
|
+
const nobleBle = createNobleBle(device);
|
|
193
|
+
const bleTransport = configureTransport(nobleBle) as any;
|
|
194
|
+
const context = {
|
|
195
|
+
messageName: 'Ping',
|
|
196
|
+
timeoutMs: 1000,
|
|
197
|
+
highVolume: false,
|
|
198
|
+
generation: 1,
|
|
199
|
+
signal: new AbortController().signal,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
await bleTransport.writeProtocolV2Frame(device.id, new Uint8Array(193), context, jest.fn());
|
|
203
|
+
|
|
204
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(2);
|
|
205
|
+
expect(nobleBle.write.mock.calls.map(([, hex]) => hex.length / 2)).toEqual([192, 1]);
|
|
206
|
+
});
|
|
207
|
+
|
|
133
208
|
test('detects Protocol V2 after Protocol V1 probe timeout', async () => {
|
|
134
209
|
const device = { id: 'unknown-pro2-id', name: 'Unknown BLE Device' };
|
|
135
210
|
const nobleBle = createNobleBle(device);
|
|
@@ -140,7 +215,6 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
140
215
|
{ message: 'ok' },
|
|
141
216
|
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
142
217
|
);
|
|
143
|
-
|
|
144
218
|
nobleBle.onNotification.mockImplementation(handler => {
|
|
145
219
|
notificationHandler = handler;
|
|
146
220
|
return jest.fn();
|
|
@@ -168,7 +242,7 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
168
242
|
}
|
|
169
243
|
});
|
|
170
244
|
|
|
171
|
-
test('
|
|
245
|
+
test('reconnects Protocol V1 with a non-destructive GetFeatures probe', async () => {
|
|
172
246
|
const device = { id: 'classic-id', name: 'OneKey Classic' };
|
|
173
247
|
const nobleBle = createNobleBle(device);
|
|
174
248
|
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
@@ -182,11 +256,12 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
182
256
|
return jest.fn();
|
|
183
257
|
});
|
|
184
258
|
nobleBle.write.mockImplementation(() => {
|
|
185
|
-
//
|
|
259
|
+
// The first write is the V1 GetFeatures probe; answer with a V1 Success response.
|
|
186
260
|
setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
|
|
187
261
|
return Promise.resolve();
|
|
188
262
|
});
|
|
189
263
|
const transport = configureTransport(nobleBle);
|
|
264
|
+
const protocolV2Writer = jest.spyOn(transport as any, 'writeProtocolV2Frame');
|
|
190
265
|
|
|
191
266
|
try {
|
|
192
267
|
await expect(transport.acquire({ uuid: device.id })).resolves.toEqual(
|
|
@@ -195,11 +270,100 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
195
270
|
})
|
|
196
271
|
);
|
|
197
272
|
expect(transport.getProtocolType(device.id)).toBe('V1');
|
|
273
|
+
await expect(transport.acquire({ uuid: device.id, expectedProtocol: 'V1' })).resolves.toEqual(
|
|
274
|
+
expect.objectContaining({
|
|
275
|
+
uuid: device.id,
|
|
276
|
+
})
|
|
277
|
+
);
|
|
278
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(2);
|
|
279
|
+
expect(nobleBle.write.mock.calls.every(([, hex]) => /^3f23230037/.test(hex))).toBe(true);
|
|
280
|
+
expect(protocolV2Writer).not.toHaveBeenCalled();
|
|
198
281
|
} finally {
|
|
199
282
|
await transport.release(device.id);
|
|
200
283
|
}
|
|
201
284
|
});
|
|
202
285
|
|
|
286
|
+
test('invalidates and disconnects a Protocol V1 link after a response timeout', async () => {
|
|
287
|
+
const device = { id: 'classic-timeout-id', name: 'OneKey Classic' };
|
|
288
|
+
const nobleBle = createNobleBle(device);
|
|
289
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
290
|
+
const v1ResponseHex = '3f23230002000000040a026f6b';
|
|
291
|
+
let writeCount = 0;
|
|
292
|
+
|
|
293
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
294
|
+
notificationHandler = handler;
|
|
295
|
+
return jest.fn();
|
|
296
|
+
});
|
|
297
|
+
nobleBle.write.mockImplementation(() => {
|
|
298
|
+
writeCount += 1;
|
|
299
|
+
if (writeCount === 1) {
|
|
300
|
+
setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
|
|
301
|
+
}
|
|
302
|
+
return Promise.resolve();
|
|
303
|
+
});
|
|
304
|
+
const bleTransport = configureTransport(nobleBle);
|
|
305
|
+
|
|
306
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V1' });
|
|
307
|
+
await expect(
|
|
308
|
+
bleTransport.call(device.id, 'Initialize', {}, { timeoutMs: 5 })
|
|
309
|
+
).rejects.toMatchObject({ errorCode: HardwareErrorCode.BleTimeoutError });
|
|
310
|
+
|
|
311
|
+
expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
|
|
312
|
+
expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
|
|
313
|
+
expect(bleTransport.getProtocolType(device.id)).toBeUndefined();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('keeps another device V2 reader when force-cleaning a V1 call', async () => {
|
|
317
|
+
const device = { id: 'classic-force-clean-id', name: 'OneKey Classic' };
|
|
318
|
+
const nobleBle = createNobleBle(device);
|
|
319
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
320
|
+
const v1ResponseHex = '3f23230002000000040a026f6b';
|
|
321
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
322
|
+
notificationHandler = handler;
|
|
323
|
+
return jest.fn();
|
|
324
|
+
});
|
|
325
|
+
nobleBle.write.mockImplementation(() => {
|
|
326
|
+
setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
|
|
327
|
+
return Promise.resolve();
|
|
328
|
+
});
|
|
329
|
+
const bleTransport = configureTransport(nobleBle) as any;
|
|
330
|
+
const activeV1Call = createDeferred<string>();
|
|
331
|
+
const otherDeviceReader = createDeferred<Uint8Array>();
|
|
332
|
+
activeV1Call.promise.catch(() => undefined);
|
|
333
|
+
otherDeviceReader.promise.catch(() => undefined);
|
|
334
|
+
bleTransport.runPromise = activeV1Call;
|
|
335
|
+
bleTransport.v2FramePromises.set('device-b', otherDeviceReader);
|
|
336
|
+
|
|
337
|
+
await bleTransport.acquire({
|
|
338
|
+
uuid: device.id,
|
|
339
|
+
expectedProtocol: 'V1',
|
|
340
|
+
forceCleanRunPromise: true,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
expect(bleTransport.v2FramePromises.get('device-b')).toBe(otherDeviceReader);
|
|
344
|
+
await bleTransport.release(device.id);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test('rejects a pending V2 reader when its device frame state resets', async () => {
|
|
348
|
+
const nobleBle = createNobleBle();
|
|
349
|
+
const bleTransport = configureTransport(nobleBle) as any;
|
|
350
|
+
const reader = createDeferred<Uint8Array>();
|
|
351
|
+
bleTransport.v2FramePromises.set('device-a', reader);
|
|
352
|
+
const result = Promise.race([
|
|
353
|
+
reader.promise.then(
|
|
354
|
+
() => 'resolved',
|
|
355
|
+
() => 'rejected'
|
|
356
|
+
),
|
|
357
|
+
new Promise(resolve => {
|
|
358
|
+
setTimeout(() => resolve('pending'), 20);
|
|
359
|
+
}),
|
|
360
|
+
]);
|
|
361
|
+
|
|
362
|
+
bleTransport.resetProtocolV2Frames('device-a');
|
|
363
|
+
|
|
364
|
+
await expect(result).resolves.toBe('rejected');
|
|
365
|
+
});
|
|
366
|
+
|
|
203
367
|
test('throws when both protocol probes fail', async () => {
|
|
204
368
|
const device = { id: 'dead-device-id', name: 'Unknown Device' };
|
|
205
369
|
const nobleBle = createNobleBle(device);
|
|
@@ -219,19 +383,21 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
219
383
|
const device = { id: 'named-pro2-id', name: 'OneKey Pro 2' };
|
|
220
384
|
const nobleBle = createNobleBle(device);
|
|
221
385
|
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
222
|
-
const probeResponse = ProtocolV2.encodeFrame(
|
|
223
|
-
schemas,
|
|
224
|
-
'Success',
|
|
225
|
-
{ message: 'ok' },
|
|
226
|
-
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
227
|
-
);
|
|
228
386
|
|
|
229
387
|
nobleBle.onNotification.mockImplementation(handler => {
|
|
230
388
|
notificationHandler = handler;
|
|
231
389
|
return jest.fn();
|
|
232
390
|
});
|
|
391
|
+
let responseSeq = 0;
|
|
233
392
|
nobleBle.write.mockImplementation(() => {
|
|
234
|
-
|
|
393
|
+
responseSeq += 1;
|
|
394
|
+
const response = ProtocolV2.encodeFrame(
|
|
395
|
+
schemas,
|
|
396
|
+
'Success',
|
|
397
|
+
{ message: 'ok' },
|
|
398
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
399
|
+
);
|
|
400
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(response)), 0);
|
|
235
401
|
return Promise.resolve();
|
|
236
402
|
});
|
|
237
403
|
const transport = configureTransport(nobleBle);
|
|
@@ -245,8 +411,226 @@ describe('ElectronBleTransport protocol detection', () => {
|
|
|
245
411
|
);
|
|
246
412
|
expect(nobleBle.write).toHaveBeenCalledTimes(1);
|
|
247
413
|
expect(transport.getProtocolType(device.id)).toBe('V2');
|
|
414
|
+
await expect(transport.call(device.id, 'Ping', { message: 'after-probe' })).resolves.toEqual({
|
|
415
|
+
type: 'Success',
|
|
416
|
+
message: { message: 'ok' },
|
|
417
|
+
});
|
|
418
|
+
const sentSeqs = nobleBle.write.mock.calls.map(([, hex]) =>
|
|
419
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
420
|
+
);
|
|
421
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
248
422
|
} finally {
|
|
249
423
|
await transport.release(device.id);
|
|
250
424
|
}
|
|
251
425
|
});
|
|
426
|
+
|
|
427
|
+
test('rejects the active Protocol V2 reader when pairing is rejected', async () => {
|
|
428
|
+
const device = { id: 'pairing-rejected-pro2-id', name: 'OneKey Pro 2' };
|
|
429
|
+
const nobleBle = createNobleBle(device);
|
|
430
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
431
|
+
let pairingRejected = false;
|
|
432
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
433
|
+
schemas,
|
|
434
|
+
'Success',
|
|
435
|
+
{ message: 'ok' },
|
|
436
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
440
|
+
notificationHandler = handler;
|
|
441
|
+
return jest.fn();
|
|
442
|
+
});
|
|
443
|
+
nobleBle.write.mockImplementation(() => {
|
|
444
|
+
setTimeout(
|
|
445
|
+
() =>
|
|
446
|
+
notificationHandler?.(
|
|
447
|
+
device.id,
|
|
448
|
+
pairingRejected ? 'PAIRING_REJECTED' : bytesToHex(probeResponse)
|
|
449
|
+
),
|
|
450
|
+
0
|
|
451
|
+
);
|
|
452
|
+
return Promise.resolve();
|
|
453
|
+
});
|
|
454
|
+
const transport = configureTransport(nobleBle);
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
await transport.acquire({ uuid: device.id });
|
|
458
|
+
pairingRejected = true;
|
|
459
|
+
|
|
460
|
+
await expect(
|
|
461
|
+
transport.call(device.id, 'Ping', { message: 'pairing' }, { timeoutMs: 50 })
|
|
462
|
+
).rejects.toMatchObject({ errorCode: HardwareErrorCode.BleDeviceBondedCanceled });
|
|
463
|
+
} finally {
|
|
464
|
+
await transport.release(device.id);
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test('rebuilds the active link when Core acquires the same device again', async () => {
|
|
469
|
+
const device = { id: 'repeated-acquire-pro2-id', name: 'OneKey Pro 2' };
|
|
470
|
+
const nobleBle = createNobleBle(device);
|
|
471
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
472
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
473
|
+
notificationHandler = handler;
|
|
474
|
+
return jest.fn();
|
|
475
|
+
});
|
|
476
|
+
let responseSeq = 0;
|
|
477
|
+
nobleBle.write.mockImplementation(() => {
|
|
478
|
+
responseSeq += 1;
|
|
479
|
+
const sequencedResponse = ProtocolV2.encodeFrame(
|
|
480
|
+
schemas,
|
|
481
|
+
'Success',
|
|
482
|
+
{ message: 'ok' },
|
|
483
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
484
|
+
);
|
|
485
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(sequencedResponse)), 0);
|
|
486
|
+
return Promise.resolve();
|
|
487
|
+
});
|
|
488
|
+
const transport = configureTransport(nobleBle);
|
|
489
|
+
|
|
490
|
+
try {
|
|
491
|
+
await transport.acquire({ uuid: device.id });
|
|
492
|
+
await transport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
493
|
+
await expect(
|
|
494
|
+
transport.call(device.id, 'Ping', { message: 'after-reacquire' })
|
|
495
|
+
).resolves.toEqual({
|
|
496
|
+
type: 'Success',
|
|
497
|
+
message: { message: 'ok' },
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
const sentSeqs = nobleBle.write.mock.calls.map(([, hex]) =>
|
|
501
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
502
|
+
);
|
|
503
|
+
expect(sentSeqs).toEqual([1, 2, 3]);
|
|
504
|
+
} finally {
|
|
505
|
+
await transport.release(device.id);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test('ignores a delayed disconnect event from the previous BLE connection', async () => {
|
|
510
|
+
const device = { id: 'delayed-disconnect-pro2-id', name: 'OneKey Pro 2' };
|
|
511
|
+
const nobleBle = createNobleBle(device);
|
|
512
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
513
|
+
const disconnectHandlers: Array<
|
|
514
|
+
(disconnectedDevice: { id: string; name: string | null }) => void
|
|
515
|
+
> = [];
|
|
516
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
517
|
+
notificationHandler = handler;
|
|
518
|
+
return jest.fn();
|
|
519
|
+
});
|
|
520
|
+
nobleBle.onDeviceDisconnected.mockImplementation(handler => {
|
|
521
|
+
disconnectHandlers.push(handler);
|
|
522
|
+
return jest.fn();
|
|
523
|
+
});
|
|
524
|
+
let responseSeq = 0;
|
|
525
|
+
nobleBle.write.mockImplementation(() => {
|
|
526
|
+
responseSeq += 1;
|
|
527
|
+
const response = ProtocolV2.encodeFrame(
|
|
528
|
+
schemas,
|
|
529
|
+
'Success',
|
|
530
|
+
{ message: 'ok' },
|
|
531
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
532
|
+
);
|
|
533
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(response)), 0);
|
|
534
|
+
return Promise.resolve();
|
|
535
|
+
});
|
|
536
|
+
const bleTransport = configureTransport(nobleBle);
|
|
537
|
+
|
|
538
|
+
try {
|
|
539
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
540
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
541
|
+
|
|
542
|
+
disconnectHandlers[0]?.(device);
|
|
543
|
+
|
|
544
|
+
expect(bleTransport.getProtocolType(device.id)).toBe('V2');
|
|
545
|
+
await expect(
|
|
546
|
+
bleTransport.call(device.id, 'Ping', { message: 'after-stale-disconnect' })
|
|
547
|
+
).resolves.toEqual({
|
|
548
|
+
type: 'Success',
|
|
549
|
+
message: { message: 'ok' },
|
|
550
|
+
});
|
|
551
|
+
} finally {
|
|
552
|
+
await bleTransport.release(device.id);
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
test('preserves the active Protocol V2 link when the same schema is configured again', async () => {
|
|
557
|
+
const device = { id: 'stable-schema-pro2-id', name: 'OneKey Pro 2' };
|
|
558
|
+
const nobleBle = createNobleBle(device);
|
|
559
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
560
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
561
|
+
notificationHandler = handler;
|
|
562
|
+
return jest.fn();
|
|
563
|
+
});
|
|
564
|
+
let responseSeq = 0;
|
|
565
|
+
nobleBle.write.mockImplementation(() => {
|
|
566
|
+
responseSeq += 1;
|
|
567
|
+
const response = ProtocolV2.encodeFrame(
|
|
568
|
+
schemas,
|
|
569
|
+
'Success',
|
|
570
|
+
{ message: 'ok' },
|
|
571
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: responseSeq }
|
|
572
|
+
);
|
|
573
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(response)), 0);
|
|
574
|
+
return Promise.resolve();
|
|
575
|
+
});
|
|
576
|
+
const bleTransport = configureTransport(nobleBle);
|
|
577
|
+
|
|
578
|
+
try {
|
|
579
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
580
|
+
const invalidateAllLinks = jest.spyOn(
|
|
581
|
+
(bleTransport as any).protocolV2Links,
|
|
582
|
+
'invalidateAllLinks'
|
|
583
|
+
);
|
|
584
|
+
bleTransport.configureProtocolV2(protocolV2Schema);
|
|
585
|
+
await new Promise<void>(resolve => {
|
|
586
|
+
setTimeout(resolve, 0);
|
|
587
|
+
});
|
|
588
|
+
expect(invalidateAllLinks).not.toHaveBeenCalled();
|
|
589
|
+
await expect(
|
|
590
|
+
bleTransport.call(device.id, 'Ping', { message: 'same-schema' })
|
|
591
|
+
).resolves.toEqual({
|
|
592
|
+
type: 'Success',
|
|
593
|
+
message: { message: 'ok' },
|
|
594
|
+
});
|
|
595
|
+
const sentSeqs = nobleBle.write.mock.calls.map(([, hex]) =>
|
|
596
|
+
Number.parseInt(hex.slice(12, 14), 16)
|
|
597
|
+
);
|
|
598
|
+
expect(sentSeqs).toEqual([1, 2]);
|
|
599
|
+
} finally {
|
|
600
|
+
await bleTransport.release(device.id);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
test('rejects oversized Protocol V2 requests before writing to Electron BLE', async () => {
|
|
605
|
+
const device = { id: 'oversized-frame-pro2-id', name: 'OneKey Pro 2' };
|
|
606
|
+
const nobleBle = createNobleBle(device);
|
|
607
|
+
let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
|
|
608
|
+
const probeResponse = ProtocolV2.encodeFrame(
|
|
609
|
+
schemas,
|
|
610
|
+
'Success',
|
|
611
|
+
{ message: 'ok' },
|
|
612
|
+
{ router: PROTOCOL_V2_CHANNEL_BLE_UART }
|
|
613
|
+
);
|
|
614
|
+
nobleBle.onNotification.mockImplementation(handler => {
|
|
615
|
+
notificationHandler = handler;
|
|
616
|
+
return jest.fn();
|
|
617
|
+
});
|
|
618
|
+
nobleBle.write.mockImplementation(() => {
|
|
619
|
+
setTimeout(() => notificationHandler?.(device.id, bytesToHex(probeResponse)), 0);
|
|
620
|
+
return Promise.resolve();
|
|
621
|
+
});
|
|
622
|
+
const bleTransport = configureTransport(nobleBle);
|
|
623
|
+
|
|
624
|
+
try {
|
|
625
|
+
await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
|
|
626
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(1);
|
|
627
|
+
|
|
628
|
+
await expect(
|
|
629
|
+
bleTransport.call(device.id, 'Ping', { message: 'x'.repeat(2048) })
|
|
630
|
+
).rejects.toThrow(/Protocol V2 frame too large for transport/);
|
|
631
|
+
expect(nobleBle.write).toHaveBeenCalledTimes(1);
|
|
632
|
+
} finally {
|
|
633
|
+
await bleTransport.release(device.id);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
252
636
|
});
|