@fww_123/uni-ble-scanner 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +283 -0
  3. package/dist/core/base-scanner.d.ts +99 -0
  4. package/dist/core/base-scanner.d.ts.map +1 -0
  5. package/dist/factory.d.ts +44 -0
  6. package/dist/factory.d.ts.map +1 -0
  7. package/dist/index.d.ts +15 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.esm.js +1791 -0
  10. package/dist/index.js +1818 -0
  11. package/dist/platforms/android-native.d.ts +30 -0
  12. package/dist/platforms/android-native.d.ts.map +1 -0
  13. package/dist/platforms/harmony-native.d.ts +24 -0
  14. package/dist/platforms/harmony-native.d.ts.map +1 -0
  15. package/dist/platforms/ios-native.d.ts +27 -0
  16. package/dist/platforms/ios-native.d.ts.map +1 -0
  17. package/dist/platforms/uniapp-scanner.d.ts +30 -0
  18. package/dist/platforms/uniapp-scanner.d.ts.map +1 -0
  19. package/dist/types.d.ts +170 -0
  20. package/dist/types.d.ts.map +1 -0
  21. package/dist/utils/data-parser.d.ts +32 -0
  22. package/dist/utils/data-parser.d.ts.map +1 -0
  23. package/dist/utils/errors.d.ts +27 -0
  24. package/dist/utils/errors.d.ts.map +1 -0
  25. package/dist/utils/permissions.d.ts +9 -0
  26. package/dist/utils/permissions.d.ts.map +1 -0
  27. package/dist/utils/platform.d.ts +18 -0
  28. package/dist/utils/platform.d.ts.map +1 -0
  29. package/package.json +58 -0
  30. package/src/core/base-scanner.ts +309 -0
  31. package/src/factory.ts +116 -0
  32. package/src/index.ts +54 -0
  33. package/src/platforms/android-native.ts +300 -0
  34. package/src/platforms/harmony-native.ts +267 -0
  35. package/src/platforms/ios-native.ts +264 -0
  36. package/src/platforms/uniapp-scanner.ts +171 -0
  37. package/src/types/global.d.ts +25 -0
  38. package/src/types/uni-types.d.ts +83 -0
  39. package/src/types.ts +178 -0
  40. package/src/utils/data-parser.ts +217 -0
  41. package/src/utils/errors.ts +105 -0
  42. package/src/utils/permissions.ts +244 -0
  43. package/src/utils/platform.ts +70 -0
@@ -0,0 +1,300 @@
1
+ import { BaseScanner } from '../core/base-scanner';
2
+ import {
3
+ Platform,
4
+ AdapterState,
5
+ BLEError,
6
+ PlatformConfig,
7
+ } from '../types';
8
+ import { Errors, createError, ErrorCodes, handlePlatformError } from '../utils/errors';
9
+
10
+ /**
11
+ * Android 原生蓝牙扫描器
12
+ * 使用 uni-app 的 Native.js 调用 Android 原生 API
13
+ */
14
+ export class AndroidNativeScanner extends BaseScanner {
15
+ private bluetoothAdapter: any = null;
16
+ private bluetoothLeScanner: any = null;
17
+ private scanCallback: any = null;
18
+ private adapterStateReceiver: any = null;
19
+
20
+ getPlatform(): Platform {
21
+ return Platform.ANDROID;
22
+ }
23
+
24
+ async init(): Promise<void> {
25
+ return new Promise((resolve, reject) => {
26
+ try {
27
+ // #ifdef APP-PLUS
28
+ const mainActivity = plus.android.runtimeMainActivity();
29
+ const BluetoothManager = plus.android.importClass('android.bluetooth.BluetoothManager');
30
+ const bluetoothManager = mainActivity.getSystemService('bluetooth');
31
+
32
+ this.bluetoothAdapter = bluetoothManager.getAdapter();
33
+
34
+ if (!this.bluetoothAdapter) {
35
+ reject(Errors.adapterNotAvailable());
36
+ return;
37
+ }
38
+
39
+ if (!this.bluetoothAdapter.isEnabled()) {
40
+ reject(Errors.bluetoothDisabled());
41
+ return;
42
+ }
43
+
44
+ this.initialized = true;
45
+ resolve();
46
+ // #endif
47
+
48
+ // #ifndef APP-PLUS
49
+ reject(createError(ErrorCodes.UNSUPPORTED_PLATFORM, '非 APP 环境不支持原生 Android 扫描'));
50
+ // #endif
51
+ } catch (error) {
52
+ reject(handlePlatformError(error, 'android'));
53
+ }
54
+ });
55
+ }
56
+
57
+ protected async startScanInternal(): Promise<void> {
58
+ return new Promise((resolve, reject) => {
59
+ try {
60
+ // #ifdef APP-PLUS
61
+ const systemInfo = uni.getSystemInfoSync();
62
+ const osVersion = parseInt(systemInfo.system?.split('.')[0] || '0');
63
+
64
+ if (osVersion >= 21 && this.bluetoothAdapter.getBluetoothLeScanner) {
65
+ // Android 5.0+ 使用新的扫描 API
66
+ this.startNewScanAPI(resolve, reject);
67
+ } else {
68
+ // 旧版扫描 API
69
+ this.startLegacyScan(resolve, reject);
70
+ }
71
+ // #endif
72
+
73
+ // #ifndef APP-PLUS
74
+ reject(Errors.unsupportedPlatform());
75
+ // #endif
76
+ } catch (error) {
77
+ reject(handlePlatformError(error, 'android'));
78
+ }
79
+ });
80
+ }
81
+
82
+ protected async stopScanInternal(): Promise<void> {
83
+ return new Promise((resolve, reject) => {
84
+ try {
85
+ // #ifdef APP-PLUS
86
+ if (this.bluetoothLeScanner && this.scanCallback) {
87
+ this.bluetoothLeScanner.stopScan(this.scanCallback);
88
+ } else if (this.bluetoothAdapter) {
89
+ this.bluetoothAdapter.stopLeScan(this.legacyScanCallback);
90
+ }
91
+ resolve();
92
+ // #endif
93
+
94
+ // #ifndef APP-PLUS
95
+ reject(Errors.unsupportedPlatform());
96
+ // #endif
97
+ } catch (error) {
98
+ reject(handlePlatformError(error, 'android'));
99
+ }
100
+ });
101
+ }
102
+
103
+ async checkPermissions(): Promise<boolean> {
104
+ // #ifdef APP-PLUS
105
+ const mainActivity = plus.android.runtimeMainActivity();
106
+ const PackageManager = plus.android.importClass('android.content.pm.PackageManager');
107
+
108
+ const systemInfo = uni.getSystemInfoSync();
109
+ const osVersion = parseInt(systemInfo.system?.split('.')[0] || '0');
110
+
111
+ let permissions: string[];
112
+ if (osVersion >= 12) {
113
+ permissions = [
114
+ 'android.permission.BLUETOOTH_SCAN',
115
+ 'android.permission.BLUETOOTH_CONNECT',
116
+ 'android.permission.ACCESS_FINE_LOCATION',
117
+ ];
118
+ } else {
119
+ permissions = [
120
+ 'android.permission.BLUETOOTH',
121
+ 'android.permission.BLUETOOTH_ADMIN',
122
+ 'android.permission.ACCESS_FINE_LOCATION',
123
+ ];
124
+ }
125
+
126
+ for (const permission of permissions) {
127
+ const result = mainActivity.checkSelfPermission(permission);
128
+ if (result !== PackageManager.PERMISSION_GRANTED) {
129
+ return false;
130
+ }
131
+ }
132
+ return true;
133
+ // #endif
134
+
135
+ // #ifndef APP-PLUS
136
+ return false;
137
+ // #endif
138
+ }
139
+
140
+ async requestPermissions(): Promise<boolean> {
141
+ // #ifdef APP-PLUS
142
+ return new Promise((resolve) => {
143
+ const systemInfo = uni.getSystemInfoSync();
144
+ const osVersion = parseInt(systemInfo.system?.split('.')[0] || '0');
145
+
146
+ let permissions: string[];
147
+ if (osVersion >= 12) {
148
+ permissions = [
149
+ 'android.permission.BLUETOOTH_SCAN',
150
+ 'android.permission.BLUETOOTH_CONNECT',
151
+ 'android.permission.ACCESS_FINE_LOCATION',
152
+ ];
153
+ } else {
154
+ permissions = [
155
+ 'android.permission.BLUETOOTH',
156
+ 'android.permission.BLUETOOTH_ADMIN',
157
+ 'android.permission.ACCESS_FINE_LOCATION',
158
+ ];
159
+ }
160
+
161
+ uni.requestAndroidPermission(
162
+ permissions[0],
163
+ (result: any) => {
164
+ resolve(result.granted);
165
+ },
166
+ () => {
167
+ resolve(false);
168
+ }
169
+ );
170
+ });
171
+ // #endif
172
+
173
+ // #ifndef APP-PLUS
174
+ return false;
175
+ // #endif
176
+ }
177
+
178
+ onAdapterStateChange(callback: (state: AdapterState) => void): void {
179
+ this.adapterStateListeners.push(callback);
180
+
181
+ // #ifdef APP-PLUS
182
+ if (!this.adapterStateReceiver) {
183
+ const mainActivity = plus.android.runtimeMainActivity();
184
+ const IntentFilter = plus.android.importClass('android.content.IntentFilter');
185
+ const BluetoothAdapter = plus.android.importClass('android.bluetooth.BluetoothAdapter');
186
+
187
+ const filter = new IntentFilter();
188
+ filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
189
+
190
+ // 这里简化处理,实际应该创建 BroadcastReceiver
191
+ // 由于 Native.js 限制,建议使用 uni-app 的标准 API 监听状态
192
+ }
193
+ // #endif
194
+ }
195
+
196
+ destroy(): void {
197
+ super.destroy();
198
+ this.bluetoothAdapter = null;
199
+ this.bluetoothLeScanner = null;
200
+ this.scanCallback = null;
201
+ }
202
+
203
+ /**
204
+ * 使用新版扫描 API (Android 5.0+)
205
+ */
206
+ private startNewScanAPI(resolve: () => void, reject: (error: BLEError) => void): void {
207
+ // #ifdef APP-PLUS
208
+ try {
209
+ const ScanSettings = plus.android.importClass('android.bluetooth.le.ScanSettings');
210
+ const ScanFilter = plus.android.importClass('android.bluetooth.le.ScanFilter');
211
+ const ScanCallback = plus.android.importClass('android.bluetooth.le.ScanCallback');
212
+
213
+ this.bluetoothLeScanner = this.bluetoothAdapter.getBluetoothLeScanner();
214
+
215
+ // 构建扫描设置
216
+ const builder = new ScanSettings.Builder();
217
+
218
+ // 设置扫描模式
219
+ const scanMode = this.platformConfig.android?.scanMode || ScanSettings.SCAN_MODE_LOW_LATENCY;
220
+ builder.setScanMode(scanMode);
221
+
222
+ const settings = builder.build();
223
+
224
+ // 构建扫描过滤器
225
+ const filters: any[] = [];
226
+ if (this.options.services && this.options.services.length > 0) {
227
+ // 可以添加服务 UUID 过滤
228
+ }
229
+
230
+ // 创建扫描回调
231
+ const self = this;
232
+ this.scanCallback = new ScanCallback({
233
+ onScanResult: (callbackType: number, result: any) => {
234
+ const device = result.getDevice();
235
+ const rssi = result.getRssi();
236
+ const scanRecord = result.getScanRecord();
237
+
238
+ self.handleDeviceFound({
239
+ deviceId: device.getAddress(),
240
+ name: device.getName(),
241
+ RSSI: rssi,
242
+ advertisData: scanRecord ? scanRecord.getBytes() : null,
243
+ });
244
+ },
245
+ onBatchScanResults: (results: any[]) => {
246
+ results.forEach((result) => {
247
+ this.scanCallback.onScanResult(0, result);
248
+ });
249
+ },
250
+ onScanFailed: (errorCode: number) => {
251
+ reject(Errors.scanFailed(`错误码: ${errorCode}`));
252
+ },
253
+ });
254
+
255
+ this.bluetoothLeScanner.startScan(filters, settings, this.scanCallback);
256
+ resolve();
257
+ } catch (error) {
258
+ reject(handlePlatformError(error, 'android'));
259
+ }
260
+ // #endif
261
+ }
262
+
263
+ /**
264
+ * 使用旧版扫描 API
265
+ */
266
+ private startLegacyScan(resolve: () => void, reject: (error: BLEError) => void): void {
267
+ // #ifdef APP-PLUS
268
+ try {
269
+ const self = this;
270
+ this.legacyScanCallback = new plus.android.implements('android.bluetooth.BluetoothAdapter.LeScanCallback', {
271
+ onLeScan: (device: any, rssi: number, scanRecord: number[]) => {
272
+ self.handleDeviceFound({
273
+ deviceId: device.getAddress(),
274
+ name: device.getName(),
275
+ RSSI: rssi,
276
+ advertisData: scanRecord,
277
+ });
278
+ },
279
+ });
280
+
281
+ let serviceUuids: string[] | null = null;
282
+ if (this.options.services && this.options.services.length > 0) {
283
+ serviceUuids = this.options.services;
284
+ }
285
+
286
+ const started = this.bluetoothAdapter.startLeScan(serviceUuids, this.legacyScanCallback);
287
+
288
+ if (started) {
289
+ resolve();
290
+ } else {
291
+ reject(Errors.scanFailed('启动扫描失败'));
292
+ }
293
+ } catch (error) {
294
+ reject(handlePlatformError(error, 'android'));
295
+ }
296
+ // #endif
297
+ }
298
+
299
+ private legacyScanCallback: any = null;
300
+ }
@@ -0,0 +1,267 @@
1
+ import { BaseScanner } from '../core/base-scanner';
2
+ import {
3
+ Platform,
4
+ AdapterState,
5
+ BLEError,
6
+ PlatformConfig,
7
+ } from '../types';
8
+ import { Errors, createError, ErrorCodes, handlePlatformError } from '../utils/errors';
9
+
10
+ /**
11
+ * 鸿蒙原生蓝牙扫描器
12
+ * 使用鸿蒙原生 API 进行蓝牙扫描
13
+ */
14
+ export class HarmonyNativeScanner extends BaseScanner {
15
+ private bluetoothManager: any = null;
16
+ private bleScanner: any = null;
17
+ private scanCallback: any = null;
18
+
19
+ getPlatform(): Platform {
20
+ return Platform.HARMONY;
21
+ }
22
+
23
+ async init(): Promise<void> {
24
+ return new Promise((resolve, reject) => {
25
+ try {
26
+ // #ifdef HARMONY
27
+ // 导入鸿蒙蓝牙模块
28
+ const bluetooth = require('@ohos.bluetooth.ble');
29
+
30
+ // 获取蓝牙管理器
31
+ this.bluetoothManager = bluetooth.BLE;
32
+
33
+ if (!this.bluetoothManager) {
34
+ reject(Errors.adapterNotAvailable());
35
+ return;
36
+ }
37
+
38
+ // 检查蓝牙是否开启
39
+ const state = this.bluetoothManager.getState();
40
+ if (state !== 2) { // STATE_ON = 2
41
+ reject(Errors.bluetoothDisabled());
42
+ return;
43
+ }
44
+
45
+ this.initialized = true;
46
+ resolve();
47
+ // #endif
48
+
49
+ // #ifndef HARMONY
50
+ reject(createError(ErrorCodes.UNSUPPORTED_PLATFORM, '非鸿蒙环境不支持原生鸿蒙扫描'));
51
+ // #endif
52
+ } catch (error) {
53
+ reject(handlePlatformError(error, 'harmony'));
54
+ }
55
+ });
56
+ }
57
+
58
+ protected async startScanInternal(): Promise<void> {
59
+ return new Promise((resolve, reject) => {
60
+ try {
61
+ // #ifdef HARMONY
62
+ const bluetooth = require('@ohos.bluetooth.ble');
63
+
64
+ // 构建扫描过滤器
65
+ const filters: any[] = [];
66
+
67
+ if (this.options.services && this.options.services.length > 0) {
68
+ this.options.services.forEach((uuid) => {
69
+ const filter = {
70
+ serviceUuid: uuid,
71
+ };
72
+ filters.push(filter);
73
+ });
74
+ }
75
+
76
+ if (this.options.nameFilter) {
77
+ const names = Array.isArray(this.options.nameFilter)
78
+ ? this.options.nameFilter
79
+ : [this.options.nameFilter];
80
+
81
+ names.forEach((name) => {
82
+ if (typeof name === 'string') {
83
+ filters.push({ deviceName: name });
84
+ }
85
+ });
86
+ }
87
+
88
+ // 构建扫描配置
89
+ const scanOptions = {
90
+ interval: 0,
91
+ dutyMode: bluetooth.ScanDuty.SCAN_MODE_LOW_LATENCY,
92
+ matchMode: bluetooth.MatchMode.MATCH_MODE_AGGRESSIVE,
93
+ };
94
+
95
+ // 创建扫描回调
96
+ const self = this;
97
+ this.scanCallback = {
98
+ onScanResult: (result: any) => {
99
+ const device = result.device;
100
+ const data = result.data;
101
+ const rssi = result.rssi;
102
+
103
+ self.handleDeviceFound({
104
+ deviceId: device.deviceId,
105
+ name: device.deviceName,
106
+ RSSI: rssi,
107
+ advertisData: data,
108
+ serviceUuids: result.serviceUuids,
109
+ });
110
+ },
111
+ onScanFailed: (errorCode: number) => {
112
+ console.error('扫描失败:', errorCode);
113
+ },
114
+ };
115
+
116
+ // 开始扫描
117
+ this.bluetoothManager.startBLEScan(filters, scanOptions, this.scanCallback);
118
+ resolve();
119
+ // #endif
120
+
121
+ // #ifndef HARMONY
122
+ reject(Errors.unsupportedPlatform());
123
+ // #endif
124
+ } catch (error) {
125
+ reject(handlePlatformError(error, 'harmony'));
126
+ }
127
+ });
128
+ }
129
+
130
+ protected async stopScanInternal(): Promise<void> {
131
+ return new Promise((resolve, reject) => {
132
+ try {
133
+ // #ifdef HARMONY
134
+ if (this.bluetoothManager) {
135
+ this.bluetoothManager.stopBLEScan();
136
+ }
137
+ resolve();
138
+ // #endif
139
+
140
+ // #ifndef HARMONY
141
+ reject(Errors.unsupportedPlatform());
142
+ // #endif
143
+ } catch (error) {
144
+ reject(handlePlatformError(error, 'harmony'));
145
+ }
146
+ });
147
+ }
148
+
149
+ async checkPermissions(): Promise<boolean> {
150
+ // #ifdef HARMONY
151
+ try {
152
+ const atManager = require('@ohos.abilityAccessCtrl').createAtManager();
153
+ const tokenId = require('@ohos.process').process.accessTokenId;
154
+
155
+ const permissions = [
156
+ 'ohos.permission.ACCESS_BLUETOOTH',
157
+ 'ohos.permission.LOCATION',
158
+ ];
159
+
160
+ for (const permission of permissions) {
161
+ const result = await atManager.verifyAccessToken(tokenId, permission);
162
+ if (result !== 0) {
163
+ return false;
164
+ }
165
+ }
166
+ return true;
167
+ } catch (error) {
168
+ console.error('检查权限失败:', error);
169
+ return false;
170
+ }
171
+ // #endif
172
+
173
+ // #ifndef HARMONY
174
+ return false;
175
+ // #endif
176
+ }
177
+
178
+ async requestPermissions(): Promise<boolean> {
179
+ // #ifdef HARMONY
180
+ try {
181
+ const atManager = require('@ohos.abilityAccessCtrl').createAtManager();
182
+ const context = getContext(this);
183
+
184
+ const permissions = [
185
+ 'ohos.permission.ACCESS_BLUETOOTH',
186
+ 'ohos.permission.LOCATION',
187
+ ];
188
+
189
+ const result = await atManager.requestPermissionsFromUser(context, permissions);
190
+ return result.authResults.every((authResult: number) => authResult === 0);
191
+ } catch (error) {
192
+ console.error('请求权限失败:', error);
193
+ return false;
194
+ }
195
+ // #endif
196
+
197
+ // #ifndef HARMONY
198
+ return false;
199
+ // #endif
200
+ }
201
+
202
+ onAdapterStateChange(callback: (state: AdapterState) => void): void {
203
+ this.adapterStateListeners.push(callback);
204
+
205
+ // #ifdef HARMONY
206
+ try {
207
+ const bluetooth = require('@ohos.bluetooth.ble');
208
+
209
+ // 监听蓝牙状态变化
210
+ bluetooth.on('stateChange', (data: any) => {
211
+ this.emitAdapterStateChange({
212
+ available: data.state === 2, // STATE_ON = 2
213
+ discovering: this.state === 'scanning',
214
+ });
215
+ });
216
+ } catch (error) {
217
+ console.error('监听状态变化失败:', error);
218
+ }
219
+ // #endif
220
+ }
221
+
222
+ destroy(): void {
223
+ super.destroy();
224
+
225
+ // #ifdef HARMONY
226
+ try {
227
+ const bluetooth = require('@ohos.bluetooth.ble');
228
+
229
+ // 停止扫描
230
+ if (this.bluetoothManager) {
231
+ this.bluetoothManager.stopBLEScan();
232
+ }
233
+
234
+ // 移除状态监听
235
+ bluetooth.off('stateChange');
236
+ } catch (error) {
237
+ console.error('销毁失败:', error);
238
+ }
239
+ // #endif
240
+
241
+ this.bluetoothManager = null;
242
+ this.scanCallback = null;
243
+ }
244
+
245
+ /**
246
+ * 获取鸿蒙蓝牙适配器状态
247
+ */
248
+ async getAdapterState(): Promise<AdapterState> {
249
+ return new Promise((resolve, reject) => {
250
+ // #ifdef HARMONY
251
+ try {
252
+ const state = this.bluetoothManager.getState();
253
+ resolve({
254
+ available: state === 2,
255
+ discovering: this.state === 'scanning',
256
+ });
257
+ } catch (error) {
258
+ reject(handlePlatformError(error, 'harmony'));
259
+ }
260
+ // #endif
261
+
262
+ // #ifndef HARMONY
263
+ reject(Errors.unsupportedPlatform());
264
+ // #endif
265
+ });
266
+ }
267
+ }