@ukeyfe/hardware-transport-react-native 1.1.13

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/src/index.ts ADDED
@@ -0,0 +1,672 @@
1
+ import { Platform, PermissionsAndroid } from 'react-native';
2
+ import { Buffer } from 'buffer';
3
+ import {
4
+ BleManager as BlePlxManager,
5
+ Device,
6
+ BleErrorCode,
7
+ Characteristic,
8
+ ScanMode,
9
+ BleATTErrorCode,
10
+ BleError,
11
+ } from 'react-native-ble-plx';
12
+ import ByteBuffer from 'bytebuffer';
13
+ import transport, { COMMON_HEADER_SIZE, LogBlockCommand } from '@ukeyfe/hardware-transport';
14
+ import {
15
+ createDeferred,
16
+ Deferred,
17
+ ERRORS,
18
+ HardwareErrorCode,
19
+ isUkeyDevice,
20
+ } from '@ukeyfe/hardware-shared';
21
+ import type EventEmitter from 'events';
22
+ import { getConnectedDeviceIds, onDeviceBondState, pairDevice } from './BleManager';
23
+ import { subscribeBleOn } from './subscribeBleOn';
24
+ import {
25
+ getBluetoothServiceUuids,
26
+ getInfosForServiceUuid,
27
+ IOS_PACKET_LENGTH,
28
+ ANDROID_PACKET_LENGTH,
29
+ } from './constants';
30
+ import { isHeaderChunk } from './utils/validateNotify';
31
+ import BleTransport from './BleTransport';
32
+ import timer from './utils/timer';
33
+ import type { BleAcquireInput, TransportOptions } from './types';
34
+
35
+ const { check, buildBuffers, receiveOne, parseConfigure } = transport;
36
+
37
+ const transportCache: Record<string, any> = {};
38
+
39
+ let connectOptions: Record<string, unknown> = {
40
+ requestMTU: 256,
41
+ timeout: 3000,
42
+ refreshGatt: 'OnConnected',
43
+ };
44
+
45
+ const tryToGetConfiguration = (device: Device) => {
46
+ if (!device || !device.serviceUUIDs) return null;
47
+ const [serviceUUID] = device.serviceUUIDs;
48
+ const infos = getInfosForServiceUuid(serviceUUID, 'classic');
49
+ if (!infos) return null;
50
+ return infos;
51
+ };
52
+
53
+ type IOBleErrorRemap = Error | BleError | null | undefined;
54
+
55
+ function remapError(error: IOBleErrorRemap) {
56
+ if (error instanceof BleError) {
57
+ if (
58
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
59
+ // @ts-expect-error
60
+ error.iosErrorCode === BleATTErrorCode.UnlikelyError ||
61
+ error.reason === 'Peer removed pairing information'
62
+ ) {
63
+ throw ERRORS.TypedError(HardwareErrorCode.BlePeerRemovedPairingInformation);
64
+ }
65
+
66
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
67
+ // @ts-ignore It's not documented but seems to match a refusal on Android pairing
68
+ if (error?.attErrorCode === 22) {
69
+ throw ERRORS.TypedError(HardwareErrorCode.BleDeviceBondError);
70
+ }
71
+ }
72
+
73
+ if (
74
+ error instanceof Error &&
75
+ error.message &&
76
+ (error.message.includes('was disconnected') || error.message.includes('not found'))
77
+ ) {
78
+ throw ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected);
79
+ }
80
+
81
+ // @ts-expect-error
82
+ throw ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.reason ?? error);
83
+ }
84
+
85
+ export default class ReactNativeBleTransport {
86
+ blePlxManager: BlePlxManager | undefined;
87
+
88
+ _messages: ReturnType<typeof transport.parseConfigure> | undefined;
89
+
90
+ name = 'ReactNativeBleTransport';
91
+
92
+ configured = false;
93
+
94
+ stopped = false;
95
+
96
+ scanTimeout = 3000;
97
+
98
+ runPromise: Deferred<any> | null = null;
99
+
100
+ Log?: any;
101
+
102
+ emitter?: EventEmitter;
103
+
104
+ constructor(options: TransportOptions) {
105
+ this.scanTimeout = options.scanTimeout ?? 3000;
106
+ }
107
+
108
+ init(logger: any, emitter: EventEmitter) {
109
+ this.Log = logger;
110
+ this.emitter = emitter;
111
+ }
112
+
113
+ configure(signedData: any) {
114
+ const messages = parseConfigure(signedData);
115
+ this.configured = true;
116
+ this._messages = messages;
117
+ }
118
+
119
+ listen() {
120
+ // empty
121
+ }
122
+
123
+ getPlxManager(): Promise<BlePlxManager> {
124
+ if (this.blePlxManager) return Promise.resolve(this.blePlxManager);
125
+ this.blePlxManager = new BlePlxManager();
126
+ return Promise.resolve(this.blePlxManager);
127
+ }
128
+
129
+ /**
130
+ * 获取设备列表
131
+ * 在搜索超过超时时间或设备数量大于 5 台时,返回 UKey 设备,
132
+ * @returns
133
+ */
134
+ async enumerate() {
135
+ // eslint-disable-next-line no-async-promise-executor
136
+ return new Promise<Device[]>(async (resolve, reject) => {
137
+ const deviceList: Device[] = [];
138
+ const blePlxManager = await this.getPlxManager();
139
+ try {
140
+ await subscribeBleOn(blePlxManager);
141
+ } catch (error) {
142
+ this.Log.debug('subscribeBleOn error: ', error);
143
+ reject(error);
144
+ return;
145
+ }
146
+
147
+ if (Platform.OS === 'android' && Platform.Version >= 31) {
148
+ this.Log.debug('requesting permissions, please wait...');
149
+
150
+ const resultConnect = await PermissionsAndroid.requestMultiple([
151
+ PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
152
+ PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
153
+ ]);
154
+
155
+ this.Log.debug('requesting permissions, result: ', resultConnect);
156
+ if (
157
+ resultConnect[PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT] !== 'granted' ||
158
+ resultConnect[PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN] !== 'granted'
159
+ ) {
160
+ reject(ERRORS.TypedError(HardwareErrorCode.BlePermissionError));
161
+ return;
162
+ }
163
+ }
164
+
165
+ blePlxManager.startDeviceScan(
166
+ null,
167
+ {
168
+ scanMode: ScanMode.LowLatency,
169
+ },
170
+ (error, device) => {
171
+ if (error) {
172
+ this.Log.debug('ble scan manager: ', blePlxManager);
173
+ this.Log.debug('ble scan error: ', error);
174
+ if (
175
+ [BleErrorCode.BluetoothPoweredOff, BleErrorCode.BluetoothInUnknownState].includes(
176
+ error.errorCode
177
+ )
178
+ ) {
179
+ reject(ERRORS.TypedError(HardwareErrorCode.BlePermissionError));
180
+ } else if (error.errorCode === BleErrorCode.BluetoothUnauthorized) {
181
+ reject(ERRORS.TypedError(HardwareErrorCode.BleLocationError));
182
+ } else if (error.errorCode === BleErrorCode.LocationServicesDisabled) {
183
+ reject(ERRORS.TypedError(HardwareErrorCode.BleLocationServicesDisabled));
184
+ } else if (error.errorCode === BleErrorCode.ScanStartFailed) {
185
+ // Android Bluetooth will report an error when the search frequency is too fast,
186
+ // then nothing is processed and an empty array of devices is returned.
187
+ // Then the next search will be back to normal
188
+ timer.timeout(() => {}, this.scanTimeout);
189
+ } else {
190
+ reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.reason ?? ''));
191
+ }
192
+ return;
193
+ }
194
+
195
+ if (isUkeyDevice(device?.name ?? null, device?.id)) {
196
+ this.Log.debug('search device start ======================');
197
+ const { name, localName, id } = device ?? {};
198
+ this.Log.debug(
199
+ `device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${id ?? ''}`
200
+ );
201
+ addDevice(device as unknown as Device);
202
+ this.Log.debug('search device end ======================\n');
203
+ }
204
+ }
205
+ );
206
+
207
+ getConnectedDeviceIds(getBluetoothServiceUuids()).then(devices => {
208
+ for (const device of devices) {
209
+ this.Log.debug('search connected peripheral: ', device.id);
210
+ addDevice(device as unknown as Device);
211
+ }
212
+ });
213
+
214
+ const addDevice = (device: Device) => {
215
+ if (deviceList.every(d => d.id !== device.id)) {
216
+ deviceList.push(device);
217
+ }
218
+ };
219
+
220
+ timer.timeout(() => {
221
+ blePlxManager.stopDeviceScan();
222
+ resolve(deviceList);
223
+ }, this.scanTimeout);
224
+ });
225
+ }
226
+
227
+ async acquire(input: BleAcquireInput) {
228
+ const { uuid, forceCleanRunPromise } = input;
229
+
230
+ if (!uuid) {
231
+ throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
232
+ }
233
+
234
+ let device: Device | null = null;
235
+
236
+ if (transportCache[uuid]) {
237
+ /**
238
+ * If the transport is not released due to an exception operation
239
+ * it will be handled again here
240
+ */
241
+ this.Log.debug('transport not be released, will release: ', uuid);
242
+ await this.release(uuid);
243
+ }
244
+
245
+ if (forceCleanRunPromise && this.runPromise) {
246
+ this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise));
247
+ this.Log.debug(
248
+ 'Force clean Bluetooth run promise, forceCleanRunPromise: ',
249
+ forceCleanRunPromise
250
+ );
251
+ }
252
+
253
+ const blePlxManager = await this.getPlxManager();
254
+ try {
255
+ await subscribeBleOn(blePlxManager);
256
+ } catch (error) {
257
+ this.Log.debug('subscribeBleOn error: ', error);
258
+ throw error;
259
+ }
260
+
261
+ // check device is bonded
262
+ if (Platform.OS === 'android') {
263
+ const bondState = await pairDevice(uuid);
264
+ if (bondState.bonding) {
265
+ await onDeviceBondState(uuid);
266
+ }
267
+ }
268
+
269
+ if (!device) {
270
+ const devices = await blePlxManager.devices([uuid]);
271
+ [device] = devices;
272
+ }
273
+
274
+ if (!device) {
275
+ const connectedDevice = await blePlxManager.connectedDevices(getBluetoothServiceUuids());
276
+ const deviceFilter = connectedDevice.filter(device => device.id === uuid);
277
+ this.Log.debug(`found connected device count: ${deviceFilter.length}`);
278
+ [device] = deviceFilter;
279
+ }
280
+
281
+ if (!device) {
282
+ this.Log.debug('try to connect to device: ', uuid);
283
+ try {
284
+ device = await blePlxManager.connectToDevice(uuid, connectOptions);
285
+ } catch (e) {
286
+ this.Log.debug('try to connect to device has error: ', e);
287
+ if (
288
+ e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
289
+ e.errorCode === BleErrorCode.OperationCancelled
290
+ ) {
291
+ connectOptions = {};
292
+ this.Log.debug('first try to reconnect without params');
293
+ device = await blePlxManager.connectToDevice(uuid);
294
+ } else if (e.errorCode === BleErrorCode.DeviceAlreadyConnected) {
295
+ this.Log.debug('device already connected');
296
+ throw ERRORS.TypedError(HardwareErrorCode.BleAlreadyConnected);
297
+ } else {
298
+ remapError(e);
299
+ }
300
+ }
301
+ }
302
+
303
+ if (!device) {
304
+ throw ERRORS.TypedError(HardwareErrorCode.BleConnectedError, 'unable to connect to device');
305
+ }
306
+
307
+ if (!(await device.isConnected())) {
308
+ this.Log.debug('not connected, try to connect to device: ', uuid);
309
+
310
+ try {
311
+ await device.connect(connectOptions);
312
+ } catch (e) {
313
+ this.Log.debug('not connected, try to connect to device has error: ', e);
314
+ if (
315
+ e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
316
+ e.errorCode === BleErrorCode.OperationCancelled
317
+ ) {
318
+ connectOptions = {};
319
+ this.Log.debug('second try to reconnect without params');
320
+ try {
321
+ await device.connect();
322
+ } catch (e) {
323
+ this.Log.debug('last try to reconnect error: ', e);
324
+ // last try to reconnect device if this issue exists
325
+ // https://github.com/dotintent/react-native-ble-plx/issues/426
326
+ if (e.errorCode === BleErrorCode.OperationCancelled) {
327
+ this.Log.debug('last try to reconnect');
328
+ await device.cancelConnection();
329
+ await device.connect();
330
+ }
331
+ }
332
+ } else {
333
+ remapError(e);
334
+ }
335
+ }
336
+ }
337
+
338
+ await device.discoverAllServicesAndCharacteristics();
339
+ let infos = tryToGetConfiguration(device);
340
+ let characteristics;
341
+
342
+ if (!infos) {
343
+ for (const serviceUuid of getBluetoothServiceUuids()) {
344
+ try {
345
+ characteristics = await device.characteristicsForService(serviceUuid);
346
+ infos = getInfosForServiceUuid(serviceUuid, 'classic');
347
+ break;
348
+ } catch (e) {
349
+ this.Log.error(e);
350
+ }
351
+ }
352
+ }
353
+
354
+ if (!infos) {
355
+ try {
356
+ this.Log.debug('cancel connection when service not found');
357
+ await device.cancelConnection();
358
+ } catch (e) {
359
+ this.Log.debug('cancel connection error when service not found: ', e.message || e.reason);
360
+ }
361
+ throw ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound);
362
+ }
363
+
364
+ const { serviceUuid, writeUuid, notifyUuid } = infos;
365
+
366
+ if (!characteristics) {
367
+ characteristics = await device.characteristicsForService(serviceUuid);
368
+ }
369
+
370
+ if (!characteristics) {
371
+ throw ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound);
372
+ }
373
+
374
+ let writeCharacteristic;
375
+ let notifyCharacteristic;
376
+ for (const c of characteristics) {
377
+ if (c.uuid === writeUuid) {
378
+ writeCharacteristic = c;
379
+ } else if (c.uuid === notifyUuid) {
380
+ notifyCharacteristic = c;
381
+ }
382
+ }
383
+
384
+ if (!writeCharacteristic) {
385
+ throw ERRORS.TypedError('BLECharacteristicNotFound: write characteristic not found');
386
+ }
387
+
388
+ if (!notifyCharacteristic) {
389
+ throw ERRORS.TypedError('BLECharacteristicNotFound: notify characteristic not found');
390
+ }
391
+
392
+ if (!writeCharacteristic.isWritableWithResponse) {
393
+ throw ERRORS.TypedError('BLECharacteristicNotWritable: write characteristic not writable');
394
+ }
395
+
396
+ if (!notifyCharacteristic.isNotifiable) {
397
+ throw ERRORS.TypedError(
398
+ 'BLECharacteristicNotNotifiable: notify characteristic not notifiable'
399
+ );
400
+ }
401
+
402
+ // release transport before new transport instance
403
+ await this.release(uuid);
404
+
405
+ const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
406
+ transport.nofitySubscription = this._monitorCharacteristic(transport.notifyCharacteristic);
407
+ transportCache[uuid] = transport;
408
+
409
+ this.emitter?.emit('device-connect', {
410
+ name: device.name,
411
+ id: device.id,
412
+ connectId: device.id,
413
+ });
414
+
415
+ const disconnectSubscription = device.onDisconnected(() => {
416
+ try {
417
+ this.Log.debug('device disconnect: ', device?.id);
418
+ this.emitter?.emit('device-disconnect', {
419
+ name: device?.name,
420
+ id: device?.id,
421
+ connectId: device?.id,
422
+ });
423
+ if (this.runPromise) {
424
+ this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError));
425
+ }
426
+ } catch (e) {
427
+ this.Log.debug('device disconnect error: ', e);
428
+ } finally {
429
+ this.release(uuid);
430
+ disconnectSubscription?.remove();
431
+ }
432
+ });
433
+
434
+ return { uuid };
435
+ }
436
+
437
+ _monitorCharacteristic(characteristic: Characteristic) {
438
+ let bufferLength = 0;
439
+ let buffer: any[] = [];
440
+ const subscription = characteristic.monitor((error, c) => {
441
+ if (error) {
442
+ this.Log.debug(
443
+ `error monitor ${characteristic.uuid}, deviceId: ${characteristic.deviceID}: ${
444
+ error as unknown as string
445
+ }`
446
+ );
447
+ if (this.runPromise) {
448
+ let ERROR:
449
+ | typeof HardwareErrorCode.BleDeviceBondError
450
+ | typeof HardwareErrorCode.BleCharacteristicNotifyError
451
+ | typeof HardwareErrorCode.BleTimeoutError =
452
+ HardwareErrorCode.BleCharacteristicNotifyError;
453
+ if (error.reason?.includes('The connection has timed out unexpectedly')) {
454
+ ERROR = HardwareErrorCode.BleTimeoutError;
455
+ }
456
+ if (error.reason?.includes('Encryption is insufficient')) {
457
+ ERROR = HardwareErrorCode.BleDeviceBondError;
458
+ }
459
+ if (
460
+ error.reason?.includes('Cannot write client characteristic config descriptor') ||
461
+ error.reason?.includes('Cannot find client characteristic config descriptor') || // pro firmware 2.3.0 upgrade
462
+ error.reason?.includes('The handle is invalid') ||
463
+ error.reason?.includes('Writing is not permitted') || // pro firmware 2.3.4 upgrade
464
+ error.reason?.includes('notify change failed for device')
465
+ ) {
466
+ this.runPromise.reject(
467
+ ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotifyChangeFailure)
468
+ );
469
+ this.Log.debug(
470
+ `${HardwareErrorCode.BleCharacteristicNotifyChangeFailure} ${error.message} ${error.reason}`
471
+ );
472
+ return;
473
+ }
474
+ this.runPromise.reject(ERRORS.TypedError(ERROR));
475
+ this.Log.debug(': monitor notify error, and has unreleased Promise', Error);
476
+ }
477
+
478
+ return;
479
+ }
480
+
481
+ if (!c) {
482
+ throw ERRORS.TypedError(HardwareErrorCode.BleMonitorError);
483
+ }
484
+
485
+ try {
486
+ const data = Buffer.from(c.value as string, 'base64');
487
+ // console.log('[hd-transport-react-native] Received a packet, ', 'buffer: ', data);
488
+ if (isHeaderChunk(data)) {
489
+ bufferLength = data.readInt32BE(5);
490
+ buffer = [...data.subarray(3)];
491
+ } else {
492
+ buffer = buffer.concat([...data]);
493
+ }
494
+
495
+ if (buffer.length - COMMON_HEADER_SIZE >= bufferLength) {
496
+ const value = Buffer.from(buffer);
497
+ // console.log(
498
+ // '[hd-transport-react-native] Received a complete packet of data, resolve Promise, this.runPromise: ',
499
+ // this.runPromise,
500
+ // 'buffer: ',
501
+ // value
502
+ // );
503
+ bufferLength = 0;
504
+ buffer = [];
505
+ this.runPromise?.resolve(value.toString('hex'));
506
+ }
507
+ } catch (error) {
508
+ this.Log.debug('monitor data error: ', error);
509
+ this.runPromise?.reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError));
510
+ }
511
+ });
512
+
513
+ return () => {
514
+ this.Log.debug('remove characteristic monitor: ', characteristic.uuid);
515
+ subscription.remove();
516
+ };
517
+ }
518
+
519
+ async release(uuid: string) {
520
+ const transport = transportCache[uuid];
521
+
522
+ if (transport) {
523
+ delete transportCache[uuid];
524
+ transport.nofitySubscription?.();
525
+ // Temporary close the Android disconnect after each request
526
+ if (Platform.OS === 'android') {
527
+ // await this.blePlxManager?.cancelDeviceConnection(uuid);
528
+ }
529
+ }
530
+
531
+ return Promise.resolve(true);
532
+ }
533
+
534
+ async post(session: string, name: string, data: Record<string, unknown>) {
535
+ await this.call(session, name, data);
536
+ }
537
+
538
+ async call(uuid: string, name: string, data: Record<string, unknown>) {
539
+ if (this.stopped) {
540
+ // eslint-disable-next-line prefer-promise-reject-errors
541
+ return Promise.reject(ERRORS.TypedError('Transport stopped.'));
542
+ }
543
+ if (this._messages == null) {
544
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
545
+ }
546
+
547
+ const forceRun = name === 'Initialize' || name === 'Cancel';
548
+
549
+ this.Log.debug('transport-react-native call this.runPromise', this.runPromise);
550
+ if (this.runPromise && !forceRun) {
551
+ throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
552
+ }
553
+
554
+ const transport = transportCache[uuid] as BleTransport;
555
+ if (!transport) {
556
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotFound);
557
+ }
558
+
559
+ this.runPromise = createDeferred();
560
+ const messages = this._messages;
561
+ // Upload resources on low-end phones may OOM
562
+ if (name === 'ResourceUpdate' || name === 'ResourceAck') {
563
+ this.Log.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', {
564
+ file_name: data?.file_name,
565
+ hash: data?.hash,
566
+ });
567
+ } else if (LogBlockCommand.has(name)) {
568
+ this.Log.debug('transport-react-native', 'call-', ' name: ', name);
569
+ } else {
570
+ this.Log.debug('transport-react-native', 'call-', ' name: ', name, ' data: ', data);
571
+ }
572
+
573
+ const buffers = buildBuffers(messages, name, data);
574
+
575
+ async function writeChunkedData(
576
+ buffers: ByteBuffer[],
577
+ writeFunction: (data: string) => Promise<void>,
578
+ onError: (e: any) => void
579
+ ) {
580
+ const packetCapacity = Platform.OS === 'ios' ? IOS_PACKET_LENGTH : ANDROID_PACKET_LENGTH;
581
+ let index = 0;
582
+ let chunk = ByteBuffer.allocate(packetCapacity);
583
+
584
+ while (index < buffers.length) {
585
+ const buffer = buffers[index].toBuffer();
586
+ chunk.append(buffer);
587
+ index += 1;
588
+
589
+ if (chunk.offset === packetCapacity || index >= buffers.length) {
590
+ chunk.reset();
591
+ try {
592
+ await writeFunction(chunk.toString('base64'));
593
+ chunk = ByteBuffer.allocate(packetCapacity);
594
+ } catch (e) {
595
+ onError(e);
596
+ throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
597
+ }
598
+ }
599
+ }
600
+ }
601
+
602
+ if (name === 'EmmcFileWrite') {
603
+ await writeChunkedData(
604
+ buffers,
605
+ data => transport.writeWithRetry(data),
606
+ e => {
607
+ this.runPromise = null;
608
+ this.Log.error('writeCharacteristic write error: ', e);
609
+ }
610
+ );
611
+ } else if (name === 'FirmwareUpload') {
612
+ await writeChunkedData(
613
+ buffers,
614
+ async data => {
615
+ await transport.writeCharacteristic.writeWithoutResponse(data);
616
+ },
617
+ e => {
618
+ this.runPromise = null;
619
+ this.Log.error('writeCharacteristic write error: ', e);
620
+ }
621
+ );
622
+ } else {
623
+ for (const o of buffers) {
624
+ const outData = o.toString('base64');
625
+ // Upload resources on low-end phones may OOM
626
+ // this.Log.debug('send hex strting: ', o.toString('hex'));
627
+ try {
628
+ await transport.writeCharacteristic.writeWithoutResponse(outData);
629
+ } catch (e) {
630
+ this.Log.debug('writeCharacteristic write error: ', e);
631
+ this.runPromise = null;
632
+ if (e.errorCode === BleErrorCode.DeviceDisconnected) {
633
+ throw ERRORS.TypedError(HardwareErrorCode.BleDeviceNotBonded);
634
+ } else if (e.errorCode === BleErrorCode.OperationStartFailed) {
635
+ throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, e.reason);
636
+ } else {
637
+ throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
638
+ }
639
+ }
640
+ }
641
+ }
642
+
643
+ try {
644
+ const response = await this.runPromise.promise;
645
+
646
+ if (typeof response !== 'string') {
647
+ throw new Error('Returning data is not string.');
648
+ }
649
+
650
+ this.Log.debug('receive data: ', response);
651
+ const jsonData = receiveOne(messages, response);
652
+ return check.call(jsonData);
653
+ } catch (e) {
654
+ this.Log.error('call error: ', e);
655
+ throw e;
656
+ } finally {
657
+ this.runPromise = null;
658
+ }
659
+ }
660
+
661
+ stop() {
662
+ this.stopped = true;
663
+ }
664
+
665
+ cancel() {
666
+ this.Log.debug('transport-react-native transport cancel');
667
+ if (this.runPromise) {
668
+ // this.runPromise.reject(new Error('Transport_CallCanceled'));
669
+ }
670
+ this.runPromise = null;
671
+ }
672
+ }
@@ -0,0 +1,26 @@
1
+ import { ERRORS, HardwareErrorCode } from '@ukeyfe/hardware-shared';
2
+ import { BlePlxManager } from './types';
3
+ import timer from './utils/timer';
4
+
5
+ export const subscribeBleOn = (bleManager: BlePlxManager, ms = 1000): Promise<void> =>
6
+ new Promise((resolve, reject) => {
7
+ let done = false;
8
+
9
+ const subscription = bleManager.onStateChange(state => {
10
+ console.log('ble state -> ', state);
11
+
12
+ if (state === 'PoweredOn') {
13
+ if (done) return;
14
+ clearTimeout();
15
+ done = true;
16
+ subscription.remove();
17
+ resolve();
18
+ }
19
+ }, true);
20
+
21
+ const clearTimeout = timer.timeout(() => {
22
+ if (done) return;
23
+ subscription.remove();
24
+ reject(ERRORS.TypedError(HardwareErrorCode.BlePermissionError));
25
+ }, ms);
26
+ });
package/src/types.ts ADDED
@@ -0,0 +1,10 @@
1
+ export type { BleManager as BlePlxManager } from 'react-native-ble-plx';
2
+
3
+ export type TransportOptions = {
4
+ scanTimeout?: number;
5
+ };
6
+
7
+ export type BleAcquireInput = {
8
+ uuid: string;
9
+ forceCleanRunPromise?: boolean;
10
+ };