@onekeyfe/hd-transport-react-native 0.0.1
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/README.md +11 -0
- package/dist/BleManager.d.ts +11 -0
- package/dist/BleManager.d.ts.map +1 -0
- package/dist/BleTransport.d.ts +11 -0
- package/dist/BleTransport.d.ts.map +1 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +487 -0
- package/dist/subscribeBleOn.d.ts +3 -0
- package/dist/subscribeBleOn.d.ts.map +1 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/deferred.d.ts +9 -0
- package/dist/utils/deferred.d.ts.map +1 -0
- package/dist/utils/timer.d.ts +5 -0
- package/dist/utils/timer.d.ts.map +1 -0
- package/dist/utils/validateNotify.d.ts +3 -0
- package/dist/utils/validateNotify.d.ts.map +1 -0
- package/package.json +27 -0
- package/src/BleManager.ts +18 -0
- package/src/BleTransport.ts +27 -0
- package/src/constants.ts +54 -0
- package/src/index.ts +398 -0
- package/src/subscribeBleOn.ts +28 -0
- package/src/types.ts +9 -0
- package/src/utils/deferred.ts +35 -0
- package/src/utils/timer.ts +24 -0
- package/src/utils/validateNotify.ts +16 -0
- package/tsconfig.json +7 -0
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const initializeBleManager: () => void;
|
|
2
|
+
export declare const getConnectedDeviceIds: (serviceUuids: string[]) => Promise<{
|
|
3
|
+
isConnectable?: boolean | undefined;
|
|
4
|
+
localName?: string | undefined;
|
|
5
|
+
manufacturerData?: any;
|
|
6
|
+
serviceUUIDs?: string[] | undefined;
|
|
7
|
+
txPowerLevel?: number | undefined;
|
|
8
|
+
id: string;
|
|
9
|
+
name: string | undefined;
|
|
10
|
+
}[]>;
|
|
11
|
+
//# sourceMappingURL=BleManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BleManager.d.ts","sourceRoot":"","sources":["../src/BleManager.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,oBAAoB,YAEhC,CAAC;AAOF,eAAO,MAAM,qBAAqB,iBAAwB,MAAM,EAAE;;;;;;;;IAMjE,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Device, Characteristic } from 'react-native-ble-plx';
|
|
2
|
+
export default class BleTransport {
|
|
3
|
+
id: string;
|
|
4
|
+
device: Device;
|
|
5
|
+
mtuSize: number;
|
|
6
|
+
writeCharacteristic: Characteristic;
|
|
7
|
+
notifyCharacteristic: Characteristic;
|
|
8
|
+
nofitySubscription?: () => void;
|
|
9
|
+
constructor(device: Device, writeCharacteristic: Characteristic, notifyCharacteristic: Characteristic);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=BleTransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BleTransport.d.ts","sourceRoot":"","sources":["../src/BleTransport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,CAAC,OAAO,OAAO,YAAY;IAC/B,EAAE,EAAE,MAAM,CAAC;IAEX,MAAM,EAAE,MAAM,CAAC;IAEf,OAAO,SAAM;IAEb,mBAAmB,EAAE,cAAc,CAAC;IAEpC,oBAAoB,EAAE,cAAc,CAAC;IAErC,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;gBAG9B,MAAM,EAAE,MAAM,EACd,mBAAmB,EAAE,cAAc,EACnC,oBAAoB,EAAE,cAAc;CAQvC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const isOnekeyDevice: (name: string | null, id?: string | undefined) => boolean;
|
|
2
|
+
export declare const getBluetoothServiceUuids: () => string[];
|
|
3
|
+
export declare const getInfosForServiceUuid: (serviceUuid: string, deviceType: 'classic') => {
|
|
4
|
+
serviceUuid: string;
|
|
5
|
+
writeUuid?: string | undefined;
|
|
6
|
+
notifyUuid?: string | undefined;
|
|
7
|
+
} | null;
|
|
8
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,SAAU,MAAM,GAAG,IAAI,8BAAgB,OAYjE,CAAC;AA8BF,eAAO,MAAM,wBAAwB,gBAA0B,CAAC;AAChE,eAAO,MAAM,sBAAsB,gBAAiB,MAAM,cAAc,SAAS;iBA1BhE,MAAM;;;QAoCtB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import transport from '@onekeyfe/hd-transport';
|
|
2
|
+
import { Device, Characteristic } from 'react-native-ble-plx';
|
|
3
|
+
|
|
4
|
+
declare type Deferred<T, I = any, D = any> = {
|
|
5
|
+
id?: I;
|
|
6
|
+
data?: D;
|
|
7
|
+
promise: Promise<T>;
|
|
8
|
+
resolve: (t: T) => void;
|
|
9
|
+
reject: (e: Error) => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
declare type TransportOptions = {
|
|
13
|
+
scanTimeout?: number;
|
|
14
|
+
};
|
|
15
|
+
declare type BleAcquireInput = {
|
|
16
|
+
uuid: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
declare class ReactNativeBleTransport {
|
|
20
|
+
_messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
21
|
+
configured: boolean;
|
|
22
|
+
stopped: boolean;
|
|
23
|
+
scanTimeout: number;
|
|
24
|
+
runPromise: Deferred<any> | null;
|
|
25
|
+
constructor(options: TransportOptions);
|
|
26
|
+
init(): void;
|
|
27
|
+
configure(signedData: any): void;
|
|
28
|
+
listen(): void;
|
|
29
|
+
enumerate(): Promise<Device[]>;
|
|
30
|
+
acquire(input: BleAcquireInput): Promise<{
|
|
31
|
+
uuid: string;
|
|
32
|
+
}>;
|
|
33
|
+
_monitorCharacteristic(characteristic: Characteristic): () => void;
|
|
34
|
+
release(uuid: string): Promise<boolean>;
|
|
35
|
+
call(uuid: string, name: string, data: Record<string, unknown>): Promise<any>;
|
|
36
|
+
stop(): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { ReactNativeBleTransport as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,SAAS,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAEL,MAAM,EAEN,cAAc,EAEf,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAAE,QAAQ,EAA4B,MAAM,kBAAkB,CAAC;AAItE,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAqBjE,MAAM,CAAC,OAAO,OAAO,uBAAuB;IAC1C,SAAS,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEnE,UAAU,UAAS;IAEnB,OAAO,UAAS;IAEhB,WAAW,SAAQ;IAEnB,UAAU,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,CAAQ;gBAE5B,OAAO,EAAE,gBAAgB;IAIrC,IAAI;IAIJ,SAAS,CAAC,UAAU,EAAE,GAAG;IAMzB,MAAM;IASA,SAAS;IA2DT,OAAO,CAAC,KAAK,EAAE,eAAe;;;IAiJpC,sBAAsB,CAAC,cAAc,EAAE,cAAc;IA8C/C,OAAO,CAAC,IAAI,EAAE,MAAM;IAepB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IA0DpE,IAAI;CAGL"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var buffer = require('buffer');
|
|
4
|
+
var transport = require('@onekeyfe/hd-transport');
|
|
5
|
+
var reactNativeBlePlx = require('react-native-ble-plx');
|
|
6
|
+
var BleManager = require('react-native-ble-manager');
|
|
7
|
+
|
|
8
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
9
|
+
|
|
10
|
+
var transport__default = /*#__PURE__*/_interopDefaultLegacy(transport);
|
|
11
|
+
var BleManager__default = /*#__PURE__*/_interopDefaultLegacy(BleManager);
|
|
12
|
+
|
|
13
|
+
/******************************************************************************
|
|
14
|
+
Copyright (c) Microsoft Corporation.
|
|
15
|
+
|
|
16
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
17
|
+
purpose with or without fee is hereby granted.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
20
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
21
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
22
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
23
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
24
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
25
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
26
|
+
***************************************************************************** */
|
|
27
|
+
|
|
28
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
29
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
30
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
31
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
32
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
33
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
34
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const initializeBleManager = () => {
|
|
39
|
+
BleManager__default["default"].start({ showAlert: false });
|
|
40
|
+
};
|
|
41
|
+
const getConnectedDeviceIds = (serviceUuids) => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
+
const connectedPeripherals = yield BleManager__default["default"].getConnectedPeripherals(serviceUuids);
|
|
43
|
+
return connectedPeripherals.map(peripheral => {
|
|
44
|
+
const { id, name, advertising = {} } = peripheral;
|
|
45
|
+
return Object.assign({ id, name }, advertising);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const timer = process.env.NODE_ENV === 'development'
|
|
50
|
+
? {
|
|
51
|
+
timeout: (fn, ms) => {
|
|
52
|
+
const startTime = Date.now();
|
|
53
|
+
const interval = setInterval(() => {
|
|
54
|
+
if (Date.now() - startTime >= ms) {
|
|
55
|
+
clearInterval(interval);
|
|
56
|
+
fn();
|
|
57
|
+
}
|
|
58
|
+
}, 100);
|
|
59
|
+
return () => {
|
|
60
|
+
clearInterval(interval);
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
: {
|
|
65
|
+
timeout: (fn, ms) => {
|
|
66
|
+
const timeout = setTimeout(fn, ms);
|
|
67
|
+
return () => clearTimeout(timeout);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const subscribeBleOn = (bleManager, ms = 3000) => new Promise((resolve, reject) => {
|
|
72
|
+
let done = false;
|
|
73
|
+
let lastState = 'Unknown';
|
|
74
|
+
const subscription = bleManager.onStateChange(state => {
|
|
75
|
+
lastState = state;
|
|
76
|
+
console.log('ble state -> ', state);
|
|
77
|
+
if (state === 'PoweredOn') {
|
|
78
|
+
if (done)
|
|
79
|
+
return;
|
|
80
|
+
clearTimeout();
|
|
81
|
+
done = true;
|
|
82
|
+
subscription.remove();
|
|
83
|
+
resolve();
|
|
84
|
+
}
|
|
85
|
+
}, true);
|
|
86
|
+
const clearTimeout = timer.timeout(() => {
|
|
87
|
+
if (done)
|
|
88
|
+
return;
|
|
89
|
+
subscription.remove();
|
|
90
|
+
reject(new Error(`Bluetooth required to be turned ${lastState}`));
|
|
91
|
+
}, ms);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const isOnekeyDevice = (name, id) => {
|
|
95
|
+
var _a;
|
|
96
|
+
if ((_a = id === null || id === void 0 ? void 0 : id.startsWith) === null || _a === void 0 ? void 0 : _a.call(id, 'MI')) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
const re = /(BixinKey\d{10})|(K\d{4})/i;
|
|
100
|
+
if (name && re.exec(name)) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
};
|
|
105
|
+
const ClassicServiceUUID = '00000001-0000-1000-8000-00805f9b34fb';
|
|
106
|
+
const OneKeyServices = {
|
|
107
|
+
classic: {
|
|
108
|
+
[ClassicServiceUUID]: {
|
|
109
|
+
serviceUuid: ClassicServiceUUID,
|
|
110
|
+
writeUuid: '00000002-0000-1000-8000-00805f9b34fb',
|
|
111
|
+
notifyUuid: '00000003-0000-1000-8000-00805f9b34fb',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
const bluetoothServices = [];
|
|
116
|
+
for (const deviceType of Object.keys(OneKeyServices)) {
|
|
117
|
+
const services = OneKeyServices[deviceType];
|
|
118
|
+
bluetoothServices.push(...Object.keys(services));
|
|
119
|
+
}
|
|
120
|
+
const getBluetoothServiceUuids = () => bluetoothServices;
|
|
121
|
+
const getInfosForServiceUuid = (serviceUuid, deviceType) => {
|
|
122
|
+
const services = OneKeyServices[deviceType];
|
|
123
|
+
if (!services) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const service = services[serviceUuid];
|
|
127
|
+
if (!service) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return service;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
function create(arg, data) {
|
|
134
|
+
let localResolve = (_t) => { };
|
|
135
|
+
let localReject = (_e) => { };
|
|
136
|
+
let id;
|
|
137
|
+
const promise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
138
|
+
localResolve = resolve;
|
|
139
|
+
localReject = reject;
|
|
140
|
+
if (typeof arg === 'function') {
|
|
141
|
+
try {
|
|
142
|
+
yield arg();
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
reject(error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (typeof arg === 'string')
|
|
149
|
+
id = arg;
|
|
150
|
+
}));
|
|
151
|
+
return {
|
|
152
|
+
id,
|
|
153
|
+
data,
|
|
154
|
+
resolve: localResolve,
|
|
155
|
+
reject: localReject,
|
|
156
|
+
promise,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const isHeaderChunk = (chunk) => {
|
|
161
|
+
if (chunk.length < 9)
|
|
162
|
+
return false;
|
|
163
|
+
const [MagicQuestionMark, sharp1, sharp2] = chunk;
|
|
164
|
+
if (String.fromCharCode(MagicQuestionMark) === String.fromCharCode(transport.MESSAGE_TOP_CHAR) &&
|
|
165
|
+
String.fromCharCode(sharp1) === String.fromCharCode(transport.MESSAGE_HEADER_BYTE) &&
|
|
166
|
+
String.fromCharCode(sharp2) === String.fromCharCode(transport.MESSAGE_HEADER_BYTE)) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
class BleTransport {
|
|
173
|
+
constructor(device, writeCharacteristic, notifyCharacteristic) {
|
|
174
|
+
this.mtuSize = 20;
|
|
175
|
+
this.id = device.id;
|
|
176
|
+
this.device = device;
|
|
177
|
+
this.writeCharacteristic = writeCharacteristic;
|
|
178
|
+
this.notifyCharacteristic = notifyCharacteristic;
|
|
179
|
+
console.log(`BleTransport(${String(this.id)}) new instance`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const { check, buildBuffer, receiveOne, parseConfigure } = transport__default["default"];
|
|
184
|
+
const blePlxManager = new reactNativeBlePlx.BleManager();
|
|
185
|
+
const transportCache = {};
|
|
186
|
+
let connectOptions = {
|
|
187
|
+
requestMTU: 1500,
|
|
188
|
+
timeout: 3000,
|
|
189
|
+
};
|
|
190
|
+
const tryToGetConfiguration = (device) => {
|
|
191
|
+
if (!device || !device.serviceUUIDs)
|
|
192
|
+
return null;
|
|
193
|
+
const [serviceUUID] = device.serviceUUIDs;
|
|
194
|
+
const infos = getInfosForServiceUuid(serviceUUID, 'classic');
|
|
195
|
+
if (!infos)
|
|
196
|
+
return null;
|
|
197
|
+
return infos;
|
|
198
|
+
};
|
|
199
|
+
class ReactNativeBleTransport {
|
|
200
|
+
constructor(options) {
|
|
201
|
+
var _a;
|
|
202
|
+
this.configured = false;
|
|
203
|
+
this.stopped = false;
|
|
204
|
+
this.scanTimeout = 3000;
|
|
205
|
+
this.runPromise = null;
|
|
206
|
+
this.scanTimeout = (_a = options.scanTimeout) !== null && _a !== void 0 ? _a : 3000;
|
|
207
|
+
}
|
|
208
|
+
init() {
|
|
209
|
+
initializeBleManager();
|
|
210
|
+
}
|
|
211
|
+
configure(signedData) {
|
|
212
|
+
const messages = parseConfigure(signedData);
|
|
213
|
+
this.configured = true;
|
|
214
|
+
this._messages = messages;
|
|
215
|
+
}
|
|
216
|
+
listen() {
|
|
217
|
+
}
|
|
218
|
+
enumerate() {
|
|
219
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
220
|
+
return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
|
|
221
|
+
const deviceList = [];
|
|
222
|
+
try {
|
|
223
|
+
yield subscribeBleOn(blePlxManager);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
console.log('subscribeBleOn error: ', error);
|
|
227
|
+
resolve([]);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
blePlxManager.startDeviceScan(null, {
|
|
231
|
+
scanMode: reactNativeBlePlx.ScanMode.LowLatency,
|
|
232
|
+
}, (error, device) => {
|
|
233
|
+
var _a;
|
|
234
|
+
if (error) {
|
|
235
|
+
console.log('ble scan manager: ', blePlxManager);
|
|
236
|
+
console.log('ble scan error: ', error);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (isOnekeyDevice((_a = device === null || device === void 0 ? void 0 : device.name) !== null && _a !== void 0 ? _a : null, device === null || device === void 0 ? void 0 : device.id)) {
|
|
240
|
+
console.log('search device start ======================');
|
|
241
|
+
const { name, localName, id } = device !== null && device !== void 0 ? device : {};
|
|
242
|
+
console.log(`device name: ${name !== null && name !== void 0 ? name : ''}\nlocalName: ${localName !== null && localName !== void 0 ? localName : ''}\nid: ${id !== null && id !== void 0 ? id : ''}`);
|
|
243
|
+
addDevice(device);
|
|
244
|
+
console.log('search device end ======================\n');
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
getConnectedDeviceIds(getBluetoothServiceUuids()).then(devices => {
|
|
248
|
+
for (const device of devices) {
|
|
249
|
+
console.log('search connected peripheral: ', device.id);
|
|
250
|
+
addDevice(device);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
const addDevice = (device) => {
|
|
254
|
+
if (deviceList.every(d => d.id !== device.id)) {
|
|
255
|
+
deviceList.push(device);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
timer.timeout(() => {
|
|
259
|
+
blePlxManager.stopDeviceScan();
|
|
260
|
+
resolve(deviceList);
|
|
261
|
+
}, this.scanTimeout);
|
|
262
|
+
}));
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
acquire(input) {
|
|
266
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
267
|
+
const { uuid } = input;
|
|
268
|
+
if (!uuid) {
|
|
269
|
+
throw new Error('uuid is required');
|
|
270
|
+
}
|
|
271
|
+
let device;
|
|
272
|
+
if (transportCache[uuid]) {
|
|
273
|
+
console.log('@onekey/hd-ble-sdk transport not be released, will release: ', uuid);
|
|
274
|
+
yield this.release(uuid);
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
yield subscribeBleOn(blePlxManager);
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
console.log('subscribeBleOn error: ', error);
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
if (!device) {
|
|
284
|
+
const devices = yield blePlxManager.devices([uuid]);
|
|
285
|
+
[device] = devices;
|
|
286
|
+
}
|
|
287
|
+
if (!device) {
|
|
288
|
+
const connectedDevice = yield blePlxManager.connectedDevices(getBluetoothServiceUuids());
|
|
289
|
+
const deviceFilter = connectedDevice.filter(device => device.id === uuid);
|
|
290
|
+
console.log(`found connected device count: ${deviceFilter.length}`);
|
|
291
|
+
[device] = deviceFilter;
|
|
292
|
+
}
|
|
293
|
+
if (!device) {
|
|
294
|
+
console.log('try to connect to device: ', uuid);
|
|
295
|
+
try {
|
|
296
|
+
device = yield blePlxManager.connectToDevice(uuid, connectOptions);
|
|
297
|
+
}
|
|
298
|
+
catch (e) {
|
|
299
|
+
console.log('try to connect to device has error: ', e);
|
|
300
|
+
if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
|
|
301
|
+
e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
|
|
302
|
+
connectOptions = {};
|
|
303
|
+
device = yield blePlxManager.connectToDevice(uuid);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
throw e;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (!device) {
|
|
311
|
+
throw new Error('unable to connect to device');
|
|
312
|
+
}
|
|
313
|
+
if (!(yield device.isConnected())) {
|
|
314
|
+
console.log('not connected, try to connect to device: ', uuid);
|
|
315
|
+
try {
|
|
316
|
+
yield device.connect(connectOptions);
|
|
317
|
+
}
|
|
318
|
+
catch (e) {
|
|
319
|
+
console.log('try to connect to device has error: ', e);
|
|
320
|
+
if (e.errorCode === reactNativeBlePlx.BleErrorCode.DeviceMTUChangeFailed ||
|
|
321
|
+
e.errorCode === reactNativeBlePlx.BleErrorCode.OperationCancelled) {
|
|
322
|
+
connectOptions = {};
|
|
323
|
+
yield device.connect();
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
throw e;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
yield device.discoverAllServicesAndCharacteristics();
|
|
331
|
+
let infos = tryToGetConfiguration(device);
|
|
332
|
+
let characteristics;
|
|
333
|
+
if (!infos) {
|
|
334
|
+
for (const serviceUuid of getBluetoothServiceUuids()) {
|
|
335
|
+
try {
|
|
336
|
+
characteristics = yield device.characteristicsForService(serviceUuid);
|
|
337
|
+
infos = getInfosForServiceUuid(serviceUuid, 'classic');
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
catch (e) {
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (!infos) {
|
|
345
|
+
throw new Error('BLEServiceNotFound: service not found');
|
|
346
|
+
}
|
|
347
|
+
const { serviceUuid, writeUuid, notifyUuid } = infos;
|
|
348
|
+
if (!characteristics) {
|
|
349
|
+
characteristics = yield device.characteristicsForService(serviceUuid);
|
|
350
|
+
}
|
|
351
|
+
if (!characteristics) {
|
|
352
|
+
throw new Error('BLEServiceNotFound: characteristics not found');
|
|
353
|
+
}
|
|
354
|
+
let writeCharacteristic;
|
|
355
|
+
let notifyCharacteristic;
|
|
356
|
+
for (const c of characteristics) {
|
|
357
|
+
if (c.uuid === writeUuid) {
|
|
358
|
+
writeCharacteristic = c;
|
|
359
|
+
}
|
|
360
|
+
else if (c.uuid === notifyUuid) {
|
|
361
|
+
notifyCharacteristic = c;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (!writeCharacteristic) {
|
|
365
|
+
throw new Error('BLECharacteristicNotFound: write characteristic not found');
|
|
366
|
+
}
|
|
367
|
+
if (!notifyCharacteristic) {
|
|
368
|
+
throw new Error('BLECharacteristicNotFound: notify characteristic not found');
|
|
369
|
+
}
|
|
370
|
+
if (!writeCharacteristic.isWritableWithResponse) {
|
|
371
|
+
throw new Error('BLECharacteristicNotWritable: write characteristic not writable');
|
|
372
|
+
}
|
|
373
|
+
if (!notifyCharacteristic.isNotifiable) {
|
|
374
|
+
throw new Error('BLECharacteristicNotNotifiable: notify characteristic not notifiable');
|
|
375
|
+
}
|
|
376
|
+
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
377
|
+
transport.nofitySubscription = this._monitorCharacteristic(transport.notifyCharacteristic);
|
|
378
|
+
transportCache[uuid] = transport;
|
|
379
|
+
device.onDisconnected(() => {
|
|
380
|
+
this.release(uuid);
|
|
381
|
+
});
|
|
382
|
+
return { uuid };
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
_monitorCharacteristic(characteristic) {
|
|
386
|
+
let bufferLength = 0;
|
|
387
|
+
let buffer$1 = [];
|
|
388
|
+
const subscription = characteristic.monitor((error, c) => {
|
|
389
|
+
var _a, _b;
|
|
390
|
+
if (error) {
|
|
391
|
+
console.log(`error monitor ${characteristic.uuid}: ${error}`);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (!c) {
|
|
395
|
+
throw new Error('Monitor Error: characteristic not found');
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
const data = buffer.Buffer.from(c.value, 'base64');
|
|
399
|
+
if (isHeaderChunk(data)) {
|
|
400
|
+
bufferLength = data.readInt32BE(5);
|
|
401
|
+
buffer$1 = [...data.subarray(3)];
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
buffer$1 = buffer$1.concat([...data]);
|
|
405
|
+
}
|
|
406
|
+
if (buffer$1.length >= bufferLength) {
|
|
407
|
+
const value = buffer.Buffer.from(buffer$1);
|
|
408
|
+
console.log('[hd-transport-react-native] Received a complete packet of data, resolve Promise, this.runPromise: ', this.runPromise, 'buffer: ', value);
|
|
409
|
+
bufferLength = 0;
|
|
410
|
+
buffer$1 = [];
|
|
411
|
+
(_a = this.runPromise) === null || _a === void 0 ? void 0 : _a.resolve(value.toString('hex'));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
console.log('monitor data error: ', error);
|
|
416
|
+
(_b = this.runPromise) === null || _b === void 0 ? void 0 : _b.reject(error);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
return () => {
|
|
420
|
+
console.log('remove characteristic monitor: ', characteristic.uuid);
|
|
421
|
+
subscription.remove();
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
release(uuid) {
|
|
425
|
+
var _a;
|
|
426
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
427
|
+
const transport = transportCache[uuid];
|
|
428
|
+
if (transport) {
|
|
429
|
+
delete transportCache[uuid];
|
|
430
|
+
(_a = transport.nofitySubscription) === null || _a === void 0 ? void 0 : _a.call(transport);
|
|
431
|
+
}
|
|
432
|
+
return Promise.resolve(true);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
call(uuid, name, data) {
|
|
436
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
437
|
+
if (this.stopped) {
|
|
438
|
+
return Promise.reject('Transport stopped.');
|
|
439
|
+
}
|
|
440
|
+
if (this._messages == null) {
|
|
441
|
+
throw new Error('Transport not configured.');
|
|
442
|
+
}
|
|
443
|
+
if (this.runPromise) {
|
|
444
|
+
throw new Error('Transport_CallInProgress');
|
|
445
|
+
}
|
|
446
|
+
const transport = transportCache[uuid];
|
|
447
|
+
if (!transport) {
|
|
448
|
+
throw new Error('Transport not found.');
|
|
449
|
+
}
|
|
450
|
+
this.runPromise = create();
|
|
451
|
+
const messages = this._messages;
|
|
452
|
+
console.log('transport-react-native', 'call-', 'messages: ', messages, ' name: ', name, ' data: ', data);
|
|
453
|
+
const o = buildBuffer(messages, name, data);
|
|
454
|
+
console.log('@onekey/hd-ble-sdk send hex strting: ', o.toString('hex'));
|
|
455
|
+
const outData = o.toString('base64');
|
|
456
|
+
try {
|
|
457
|
+
yield transport.writeCharacteristic.writeWithResponse(outData);
|
|
458
|
+
}
|
|
459
|
+
catch (e) {
|
|
460
|
+
this.runPromise = null;
|
|
461
|
+
console.log('writeCharacteristic write error: ', e);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
const response = yield this.runPromise.promise;
|
|
466
|
+
if (typeof response !== 'string') {
|
|
467
|
+
throw new Error('Returning data is not string.');
|
|
468
|
+
}
|
|
469
|
+
console.log('@onekey/hd-ble-sdk receive data: ', response);
|
|
470
|
+
const jsonData = receiveOne(messages, response);
|
|
471
|
+
return check.call(jsonData);
|
|
472
|
+
}
|
|
473
|
+
catch (e) {
|
|
474
|
+
console.log('call error: ', e);
|
|
475
|
+
return e;
|
|
476
|
+
}
|
|
477
|
+
finally {
|
|
478
|
+
this.runPromise = null;
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
stop() {
|
|
483
|
+
this.stopped = true;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
module.exports = ReactNativeBleTransport;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscribeBleOn.d.ts","sourceRoot":"","sources":["../src/subscribeBleOn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGxC,eAAO,MAAM,cAAc,eAAgB,aAAa,kBAAc,QAAQ,IAAI,CAwB9E,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,IAAI,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAExE,oBAAY,gBAAgB,GAAG;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,oBAAY,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare type Deferred<T, I = any, D = any> = {
|
|
2
|
+
id?: I;
|
|
3
|
+
data?: D;
|
|
4
|
+
promise: Promise<T>;
|
|
5
|
+
resolve: (t: T) => void;
|
|
6
|
+
reject: (e: Error) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function create<T, I = any, D = any>(arg?: I, data?: D): Deferred<T, I, D>;
|
|
9
|
+
//# sourceMappingURL=deferred.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred.d.ts","sourceRoot":"","sources":["../../src/utils/deferred.ts"],"names":[],"mappings":"AAAA,oBAAY,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,IAAI;IAC1C,EAAE,CAAC,EAAE,CAAC,CAAC;IACP,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC;IACxB,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;CAC5B,CAAC;AAEF,wBAAgB,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CA0BhF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timer.d.ts","sourceRoot":"","sources":["../../src/utils/timer.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,KAAK;4BAGqB,MAAM,GAAG,CAAC,KAAK,GAAG,MAAM,MAAM;CAkBvD,CAAC;AAER,eAAe,KAAK,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateNotify.d.ts","sourceRoot":"","sources":["../../src/utils/validateNotify.ts"],"names":[],"mappings":";AAEA,eAAO,MAAM,aAAa,UAAW,MAAM,KAAG,OAa7C,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@onekeyfe/hd-transport-react-native",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/OneKeyHQ/hardware-js-sdk.git"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "echo \"Error: run tests from root\" && exit 1",
|
|
17
|
+
"build": "rimraf dist && rollup -c ../../build/rollup.config.js",
|
|
18
|
+
"lint": "eslint .",
|
|
19
|
+
"lint:fix": "eslint . --fix"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@onekeyfe/hd-transport": "^0.0.1",
|
|
23
|
+
"react-native-ble-manager": "^8.1.0",
|
|
24
|
+
"react-native-ble-plx": "^2.0.3"
|
|
25
|
+
},
|
|
26
|
+
"gitHead": "af17c6b173176bd015b5c9dad3b1d20a734dd49b"
|
|
27
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import BleManager from 'react-native-ble-manager';
|
|
2
|
+
|
|
3
|
+
export const initializeBleManager = () => {
|
|
4
|
+
BleManager.start({ showAlert: false });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* get the device basic info of connected devices
|
|
9
|
+
* @param serviceUuids
|
|
10
|
+
* @returns {Promise<[string[]]>}
|
|
11
|
+
*/
|
|
12
|
+
export const getConnectedDeviceIds = async (serviceUuids: string[]) => {
|
|
13
|
+
const connectedPeripherals = await BleManager.getConnectedPeripherals(serviceUuids);
|
|
14
|
+
return connectedPeripherals.map(peripheral => {
|
|
15
|
+
const { id, name, advertising = {} } = peripheral;
|
|
16
|
+
return { id, name, ...advertising };
|
|
17
|
+
});
|
|
18
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Device, Characteristic } from 'react-native-ble-plx';
|
|
2
|
+
|
|
3
|
+
export default class BleTransport {
|
|
4
|
+
id: string;
|
|
5
|
+
|
|
6
|
+
device: Device;
|
|
7
|
+
|
|
8
|
+
mtuSize = 20;
|
|
9
|
+
|
|
10
|
+
writeCharacteristic: Characteristic;
|
|
11
|
+
|
|
12
|
+
notifyCharacteristic: Characteristic;
|
|
13
|
+
|
|
14
|
+
nofitySubscription?: () => void;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
device: Device,
|
|
18
|
+
writeCharacteristic: Characteristic,
|
|
19
|
+
notifyCharacteristic: Characteristic
|
|
20
|
+
) {
|
|
21
|
+
this.id = device.id;
|
|
22
|
+
this.device = device;
|
|
23
|
+
this.writeCharacteristic = writeCharacteristic;
|
|
24
|
+
this.notifyCharacteristic = notifyCharacteristic;
|
|
25
|
+
console.log(`BleTransport(${String(this.id)}) new instance`);
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const isOnekeyDevice = (name: string | null, id?: string): boolean => {
|
|
2
|
+
if (id?.startsWith?.('MI')) {
|
|
3
|
+
return true;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// 过滤 BixinKeyxxx 和 Kxxxx
|
|
7
|
+
// i 忽略大小写模式
|
|
8
|
+
const re = /(BixinKey\d{10})|(K\d{4})/i;
|
|
9
|
+
if (name && re.exec(name)) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type BluetoothServices = Record<
|
|
16
|
+
string,
|
|
17
|
+
{
|
|
18
|
+
serviceUuid: string;
|
|
19
|
+
writeUuid?: string;
|
|
20
|
+
notifyUuid?: string;
|
|
21
|
+
}
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
const ClassicServiceUUID = '00000001-0000-1000-8000-00805f9b34fb';
|
|
25
|
+
|
|
26
|
+
const OneKeyServices: Record<string, BluetoothServices> = {
|
|
27
|
+
classic: {
|
|
28
|
+
[ClassicServiceUUID]: {
|
|
29
|
+
serviceUuid: ClassicServiceUUID,
|
|
30
|
+
writeUuid: '00000002-0000-1000-8000-00805f9b34fb',
|
|
31
|
+
notifyUuid: '00000003-0000-1000-8000-00805f9b34fb',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const bluetoothServices: string[] = [];
|
|
37
|
+
|
|
38
|
+
for (const deviceType of Object.keys(OneKeyServices)) {
|
|
39
|
+
const services = OneKeyServices[deviceType];
|
|
40
|
+
bluetoothServices.push(...Object.keys(services));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const getBluetoothServiceUuids = () => bluetoothServices;
|
|
44
|
+
export const getInfosForServiceUuid = (serviceUuid: string, deviceType: 'classic') => {
|
|
45
|
+
const services = OneKeyServices[deviceType];
|
|
46
|
+
if (!services) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const service = services[serviceUuid];
|
|
50
|
+
if (!service) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return service;
|
|
54
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import transport from '@onekeyfe/hd-transport';
|
|
3
|
+
import {
|
|
4
|
+
BleManager as BlePlxManager,
|
|
5
|
+
Device,
|
|
6
|
+
BleErrorCode,
|
|
7
|
+
Characteristic,
|
|
8
|
+
ScanMode,
|
|
9
|
+
} from 'react-native-ble-plx';
|
|
10
|
+
import { initializeBleManager, getConnectedDeviceIds } from './BleManager';
|
|
11
|
+
import { subscribeBleOn } from './subscribeBleOn';
|
|
12
|
+
import { isOnekeyDevice, getBluetoothServiceUuids, getInfosForServiceUuid } from './constants';
|
|
13
|
+
import { Deferred, create as createDeferred } from './utils/deferred';
|
|
14
|
+
import { isHeaderChunk } from './utils/validateNotify';
|
|
15
|
+
import BleTransport from './BleTransport';
|
|
16
|
+
import timer from './utils/timer';
|
|
17
|
+
import type { BleAcquireInput, TransportOptions } from './types';
|
|
18
|
+
|
|
19
|
+
const { check, buildBuffer, receiveOne, parseConfigure } = transport;
|
|
20
|
+
|
|
21
|
+
const blePlxManager = new BlePlxManager();
|
|
22
|
+
|
|
23
|
+
const transportCache: Record<string, any> = {};
|
|
24
|
+
|
|
25
|
+
let connectOptions: Record<string, unknown> = {
|
|
26
|
+
requestMTU: 1500,
|
|
27
|
+
timeout: 3000,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const tryToGetConfiguration = (device: Device) => {
|
|
31
|
+
if (!device || !device.serviceUUIDs) return null;
|
|
32
|
+
const [serviceUUID] = device.serviceUUIDs;
|
|
33
|
+
const infos = getInfosForServiceUuid(serviceUUID, 'classic');
|
|
34
|
+
if (!infos) return null;
|
|
35
|
+
return infos;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default class ReactNativeBleTransport {
|
|
39
|
+
_messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
40
|
+
|
|
41
|
+
configured = false;
|
|
42
|
+
|
|
43
|
+
stopped = false;
|
|
44
|
+
|
|
45
|
+
scanTimeout = 3000;
|
|
46
|
+
|
|
47
|
+
runPromise: Deferred<any> | null = null;
|
|
48
|
+
|
|
49
|
+
constructor(options: TransportOptions) {
|
|
50
|
+
this.scanTimeout = options.scanTimeout ?? 3000;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
init() {
|
|
54
|
+
initializeBleManager();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
configure(signedData: any) {
|
|
58
|
+
const messages = parseConfigure(signedData);
|
|
59
|
+
this.configured = true;
|
|
60
|
+
this._messages = messages;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
listen() {
|
|
64
|
+
// empty
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 获取设备列表
|
|
69
|
+
* 在搜索超过超时时间或设备数量大于 5 台时,返回 OneKey 设备,
|
|
70
|
+
* @returns
|
|
71
|
+
*/
|
|
72
|
+
async enumerate() {
|
|
73
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
74
|
+
return new Promise<Device[]>(async resolve => {
|
|
75
|
+
const deviceList: Device[] = [];
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await subscribeBleOn(blePlxManager);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.log('subscribeBleOn error: ', error);
|
|
81
|
+
resolve([]);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
blePlxManager.startDeviceScan(
|
|
86
|
+
null,
|
|
87
|
+
{
|
|
88
|
+
scanMode: ScanMode.LowLatency,
|
|
89
|
+
},
|
|
90
|
+
(error, device) => {
|
|
91
|
+
if (error) {
|
|
92
|
+
console.log('ble scan manager: ', blePlxManager);
|
|
93
|
+
console.log('ble scan error: ', error);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (isOnekeyDevice(device?.name ?? null, device?.id)) {
|
|
98
|
+
console.log('search device start ======================');
|
|
99
|
+
|
|
100
|
+
const { name, localName, id } = device ?? {};
|
|
101
|
+
console.log(
|
|
102
|
+
`device name: ${name ?? ''}\nlocalName: ${localName ?? ''}\nid: ${id ?? ''}`
|
|
103
|
+
);
|
|
104
|
+
addDevice(device as unknown as Device);
|
|
105
|
+
|
|
106
|
+
console.log('search device end ======================\n');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
getConnectedDeviceIds(getBluetoothServiceUuids()).then(devices => {
|
|
112
|
+
for (const device of devices) {
|
|
113
|
+
console.log('search connected peripheral: ', device.id);
|
|
114
|
+
addDevice(device as unknown as Device);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const addDevice = (device: Device) => {
|
|
119
|
+
if (deviceList.every(d => d.id !== device.id)) {
|
|
120
|
+
deviceList.push(device);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
timer.timeout(() => {
|
|
125
|
+
blePlxManager.stopDeviceScan();
|
|
126
|
+
resolve(deviceList);
|
|
127
|
+
}, this.scanTimeout);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async acquire(input: BleAcquireInput) {
|
|
132
|
+
const { uuid } = input;
|
|
133
|
+
|
|
134
|
+
if (!uuid) {
|
|
135
|
+
throw new Error('uuid is required');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let device;
|
|
139
|
+
|
|
140
|
+
if (transportCache[uuid]) {
|
|
141
|
+
/**
|
|
142
|
+
* If the transport is not released due to an exception operation
|
|
143
|
+
* it will be handled again here
|
|
144
|
+
*/
|
|
145
|
+
console.log('@onekey/hd-ble-sdk transport not be released, will release: ', uuid);
|
|
146
|
+
await this.release(uuid);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
await subscribeBleOn(blePlxManager);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.log('subscribeBleOn error: ', error);
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!device) {
|
|
157
|
+
const devices = await blePlxManager.devices([uuid]);
|
|
158
|
+
[device] = devices;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!device) {
|
|
162
|
+
const connectedDevice = await blePlxManager.connectedDevices(getBluetoothServiceUuids());
|
|
163
|
+
const deviceFilter = connectedDevice.filter(device => device.id === uuid);
|
|
164
|
+
console.log(`found connected device count: ${deviceFilter.length}`);
|
|
165
|
+
[device] = deviceFilter;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!device) {
|
|
169
|
+
console.log('try to connect to device: ', uuid);
|
|
170
|
+
try {
|
|
171
|
+
device = await blePlxManager.connectToDevice(uuid, connectOptions);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
console.log('try to connect to device has error: ', e);
|
|
174
|
+
if (
|
|
175
|
+
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
176
|
+
e.errorCode === BleErrorCode.OperationCancelled
|
|
177
|
+
) {
|
|
178
|
+
connectOptions = {};
|
|
179
|
+
device = await blePlxManager.connectToDevice(uuid);
|
|
180
|
+
} else {
|
|
181
|
+
throw e;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!device) {
|
|
187
|
+
throw new Error('unable to connect to device');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!(await device.isConnected())) {
|
|
191
|
+
console.log('not connected, try to connect to device: ', uuid);
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
await device.connect(connectOptions);
|
|
195
|
+
} catch (e) {
|
|
196
|
+
console.log('try to connect to device has error: ', e);
|
|
197
|
+
if (
|
|
198
|
+
e.errorCode === BleErrorCode.DeviceMTUChangeFailed ||
|
|
199
|
+
e.errorCode === BleErrorCode.OperationCancelled
|
|
200
|
+
) {
|
|
201
|
+
connectOptions = {};
|
|
202
|
+
await device.connect();
|
|
203
|
+
} else {
|
|
204
|
+
throw e;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
await device.discoverAllServicesAndCharacteristics();
|
|
210
|
+
let infos = tryToGetConfiguration(device);
|
|
211
|
+
let characteristics;
|
|
212
|
+
|
|
213
|
+
if (!infos) {
|
|
214
|
+
for (const serviceUuid of getBluetoothServiceUuids()) {
|
|
215
|
+
try {
|
|
216
|
+
characteristics = await device.characteristicsForService(serviceUuid);
|
|
217
|
+
infos = getInfosForServiceUuid(serviceUuid, 'classic');
|
|
218
|
+
break;
|
|
219
|
+
} catch (e) {
|
|
220
|
+
// empty
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!infos) {
|
|
226
|
+
throw new Error('BLEServiceNotFound: service not found');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const { serviceUuid, writeUuid, notifyUuid } = infos;
|
|
230
|
+
|
|
231
|
+
if (!characteristics) {
|
|
232
|
+
characteristics = await device.characteristicsForService(serviceUuid);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!characteristics) {
|
|
236
|
+
throw new Error('BLEServiceNotFound: characteristics not found');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let writeCharacteristic;
|
|
240
|
+
let notifyCharacteristic;
|
|
241
|
+
for (const c of characteristics) {
|
|
242
|
+
if (c.uuid === writeUuid) {
|
|
243
|
+
writeCharacteristic = c;
|
|
244
|
+
} else if (c.uuid === notifyUuid) {
|
|
245
|
+
notifyCharacteristic = c;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!writeCharacteristic) {
|
|
250
|
+
throw new Error('BLECharacteristicNotFound: write characteristic not found');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!notifyCharacteristic) {
|
|
254
|
+
throw new Error('BLECharacteristicNotFound: notify characteristic not found');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!writeCharacteristic.isWritableWithResponse) {
|
|
258
|
+
throw new Error('BLECharacteristicNotWritable: write characteristic not writable');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!notifyCharacteristic.isNotifiable) {
|
|
262
|
+
throw new Error('BLECharacteristicNotNotifiable: notify characteristic not notifiable');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const transport = new BleTransport(device, writeCharacteristic, notifyCharacteristic);
|
|
266
|
+
transport.nofitySubscription = this._monitorCharacteristic(transport.notifyCharacteristic);
|
|
267
|
+
transportCache[uuid] = transport;
|
|
268
|
+
|
|
269
|
+
device.onDisconnected(() => {
|
|
270
|
+
this.release(uuid);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
return { uuid };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
_monitorCharacteristic(characteristic: Characteristic) {
|
|
277
|
+
let bufferLength = 0;
|
|
278
|
+
let buffer: any[] = [];
|
|
279
|
+
const subscription = characteristic.monitor((error, c) => {
|
|
280
|
+
if (error) {
|
|
281
|
+
console.log(`error monitor ${characteristic.uuid}: ${error as unknown as string}`);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!c) {
|
|
286
|
+
throw new Error('Monitor Error: characteristic not found');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const data = Buffer.from(c.value as string, 'base64');
|
|
291
|
+
if (isHeaderChunk(data)) {
|
|
292
|
+
bufferLength = data.readInt32BE(5);
|
|
293
|
+
buffer = [...data.subarray(3)];
|
|
294
|
+
} else {
|
|
295
|
+
buffer = buffer.concat([...data]);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (buffer.length >= bufferLength) {
|
|
299
|
+
const value = Buffer.from(buffer);
|
|
300
|
+
console.log(
|
|
301
|
+
'[hd-transport-react-native] Received a complete packet of data, resolve Promise, this.runPromise: ',
|
|
302
|
+
this.runPromise,
|
|
303
|
+
'buffer: ',
|
|
304
|
+
value
|
|
305
|
+
);
|
|
306
|
+
bufferLength = 0;
|
|
307
|
+
buffer = [];
|
|
308
|
+
this.runPromise?.resolve(value.toString('hex'));
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.log('monitor data error: ', error);
|
|
312
|
+
this.runPromise?.reject(error);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return () => {
|
|
317
|
+
console.log('remove characteristic monitor: ', characteristic.uuid);
|
|
318
|
+
subscription.remove();
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async release(uuid: string) {
|
|
323
|
+
const transport = transportCache[uuid];
|
|
324
|
+
|
|
325
|
+
if (transport) {
|
|
326
|
+
delete transportCache[uuid];
|
|
327
|
+
transport.nofitySubscription?.();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* The current strategy does not require disconnection
|
|
332
|
+
*/
|
|
333
|
+
// await blePlxManager.cancelDeviceConnection(uuid);
|
|
334
|
+
return Promise.resolve(true);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async call(uuid: string, name: string, data: Record<string, unknown>) {
|
|
338
|
+
if (this.stopped) {
|
|
339
|
+
// eslint-disable-next-line prefer-promise-reject-errors
|
|
340
|
+
return Promise.reject('Transport stopped.');
|
|
341
|
+
}
|
|
342
|
+
if (this._messages == null) {
|
|
343
|
+
throw new Error('Transport not configured.');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (this.runPromise) {
|
|
347
|
+
throw new Error('Transport_CallInProgress');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const transport = transportCache[uuid] as BleTransport;
|
|
351
|
+
if (!transport) {
|
|
352
|
+
throw new Error('Transport not found.');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
this.runPromise = createDeferred();
|
|
356
|
+
const messages = this._messages;
|
|
357
|
+
console.log(
|
|
358
|
+
'transport-react-native',
|
|
359
|
+
'call-',
|
|
360
|
+
'messages: ',
|
|
361
|
+
messages,
|
|
362
|
+
' name: ',
|
|
363
|
+
name,
|
|
364
|
+
' data: ',
|
|
365
|
+
data
|
|
366
|
+
);
|
|
367
|
+
const o = buildBuffer(messages, name, data);
|
|
368
|
+
console.log('@onekey/hd-ble-sdk send hex strting: ', o.toString('hex'));
|
|
369
|
+
const outData = o.toString('base64');
|
|
370
|
+
try {
|
|
371
|
+
await transport.writeCharacteristic.writeWithResponse(outData);
|
|
372
|
+
} catch (e) {
|
|
373
|
+
this.runPromise = null;
|
|
374
|
+
console.log('writeCharacteristic write error: ', e);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
const response = await this.runPromise.promise;
|
|
379
|
+
|
|
380
|
+
if (typeof response !== 'string') {
|
|
381
|
+
throw new Error('Returning data is not string.');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
console.log('@onekey/hd-ble-sdk receive data: ', response);
|
|
385
|
+
const jsonData = receiveOne(messages, response);
|
|
386
|
+
return check.call(jsonData);
|
|
387
|
+
} catch (e) {
|
|
388
|
+
console.log('call error: ', e);
|
|
389
|
+
return e;
|
|
390
|
+
} finally {
|
|
391
|
+
this.runPromise = null;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
stop() {
|
|
396
|
+
this.stopped = true;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BlePlxManager } from './types';
|
|
2
|
+
import timer from './utils/timer';
|
|
3
|
+
|
|
4
|
+
export const subscribeBleOn = (bleManager: BlePlxManager, ms = 3000): Promise<void> =>
|
|
5
|
+
new Promise((resolve, reject) => {
|
|
6
|
+
let done = false;
|
|
7
|
+
let lastState = 'Unknown';
|
|
8
|
+
|
|
9
|
+
const subscription = bleManager.onStateChange(state => {
|
|
10
|
+
lastState = state;
|
|
11
|
+
|
|
12
|
+
console.log('ble state -> ', state);
|
|
13
|
+
|
|
14
|
+
if (state === 'PoweredOn') {
|
|
15
|
+
if (done) return;
|
|
16
|
+
clearTimeout();
|
|
17
|
+
done = true;
|
|
18
|
+
subscription.remove();
|
|
19
|
+
resolve();
|
|
20
|
+
}
|
|
21
|
+
}, true);
|
|
22
|
+
|
|
23
|
+
const clearTimeout = timer.timeout(() => {
|
|
24
|
+
if (done) return;
|
|
25
|
+
subscription.remove();
|
|
26
|
+
reject(new Error(`Bluetooth required to be turned ${lastState}`));
|
|
27
|
+
}, ms);
|
|
28
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type Deferred<T, I = any, D = any> = {
|
|
2
|
+
id?: I;
|
|
3
|
+
data?: D;
|
|
4
|
+
promise: Promise<T>;
|
|
5
|
+
resolve: (t: T) => void;
|
|
6
|
+
reject: (e: Error) => void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function create<T, I = any, D = any>(arg?: I, data?: D): Deferred<T, I, D> {
|
|
10
|
+
let localResolve: (t: T) => void = (_t: T) => {};
|
|
11
|
+
let localReject: (e?: Error) => void = (_e?: Error) => {};
|
|
12
|
+
let id: I | undefined;
|
|
13
|
+
|
|
14
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
15
|
+
const promise: Promise<T> = new Promise(async (resolve, reject) => {
|
|
16
|
+
localResolve = resolve;
|
|
17
|
+
localReject = reject;
|
|
18
|
+
if (typeof arg === 'function') {
|
|
19
|
+
try {
|
|
20
|
+
await arg();
|
|
21
|
+
} catch (error) {
|
|
22
|
+
reject(error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (typeof arg === 'string') id = arg;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
id,
|
|
30
|
+
data,
|
|
31
|
+
resolve: localResolve,
|
|
32
|
+
reject: localReject,
|
|
33
|
+
promise,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const timer =
|
|
2
|
+
process.env.NODE_ENV === 'development'
|
|
3
|
+
? {
|
|
4
|
+
timeout: (fn: (...args: Array<any>) => any, ms: number) => {
|
|
5
|
+
const startTime = Date.now();
|
|
6
|
+
const interval = setInterval(() => {
|
|
7
|
+
if (Date.now() - startTime >= ms) {
|
|
8
|
+
clearInterval(interval);
|
|
9
|
+
fn();
|
|
10
|
+
}
|
|
11
|
+
}, 100);
|
|
12
|
+
return () => {
|
|
13
|
+
clearInterval(interval);
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
: {
|
|
18
|
+
timeout: (fn: (...args: Array<any>) => any, ms: number) => {
|
|
19
|
+
const timeout = setTimeout(fn, ms);
|
|
20
|
+
return () => clearTimeout(timeout);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default timer;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MESSAGE_TOP_CHAR, MESSAGE_HEADER_BYTE } from '@onekeyfe/hd-transport';
|
|
2
|
+
|
|
3
|
+
export const isHeaderChunk = (chunk: Buffer): boolean => {
|
|
4
|
+
if (chunk.length < 9) return false;
|
|
5
|
+
const [MagicQuestionMark, sharp1, sharp2] = chunk;
|
|
6
|
+
|
|
7
|
+
if (
|
|
8
|
+
String.fromCharCode(MagicQuestionMark) === String.fromCharCode(MESSAGE_TOP_CHAR) &&
|
|
9
|
+
String.fromCharCode(sharp1) === String.fromCharCode(MESSAGE_HEADER_BYTE) &&
|
|
10
|
+
String.fromCharCode(sharp2) === String.fromCharCode(MESSAGE_HEADER_BYTE)
|
|
11
|
+
) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return false;
|
|
16
|
+
};
|