@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,264 @@
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
+ * iOS 原生蓝牙扫描器
12
+ * 使用 uni-app 的 Native.js 调用 iOS CoreBluetooth API
13
+ */
14
+ export class IOSNativeScanner extends BaseScanner {
15
+ private centralManager: any = null;
16
+ private delegate: any = null;
17
+
18
+ getPlatform(): Platform {
19
+ return Platform.IOS;
20
+ }
21
+
22
+ async init(): Promise<void> {
23
+ return new Promise((resolve, reject) => {
24
+ try {
25
+ // #ifdef APP-PLUS
26
+ // 导入 iOS CoreBluetooth 框架
27
+ const CBCentralManager = plus.ios.importClass('CBCentralManager');
28
+ const CBUUID = plus.ios.importClass('CBUUID');
29
+ const dispatch_queue_attr_make_with_qos_class = plus.ios.importClass('dispatch_queue_attr_make_with_qos_class');
30
+ const dispatch_queue_create = plus.ios.importClass('dispatch_queue_create');
31
+
32
+ // 创建队列
33
+ const queue = dispatch_queue_create('com.ble.scanner', null);
34
+
35
+ // 创建中央管理器
36
+ this.centralManager = new CBCentralManager({
37
+ queue: queue,
38
+ options: {
39
+ 'CBCentralManagerOptionShowPowerAlertKey': true,
40
+ 'CBCentralManagerOptionRestoreIdentifierKey': this.platformConfig.ios?.restoreIdentifier,
41
+ },
42
+ });
43
+
44
+ if (!this.centralManager) {
45
+ reject(Errors.adapterNotAvailable());
46
+ return;
47
+ }
48
+
49
+ // 设置委托
50
+ const self = this;
51
+ this.delegate = plus.ios.implements('CBCentralManagerDelegate', {
52
+ centralManagerDidUpdateState: (central: any) => {
53
+ const state = central.state();
54
+ // CBManagerStatePoweredOn = 5
55
+ if (state === 5) {
56
+ self.initialized = true;
57
+ resolve();
58
+ } else if (state === 4) { // CBManagerStatePoweredOff
59
+ reject(Errors.bluetoothDisabled());
60
+ } else {
61
+ reject(createError(
62
+ ErrorCodes.ADAPTER_NOT_AVAILABLE,
63
+ `蓝牙适配器状态异常: ${state}`
64
+ ));
65
+ }
66
+ },
67
+ centralManagerDidDiscoverPeripheralAdvertisementDataRSSI: (
68
+ central: any,
69
+ peripheral: any,
70
+ advertisementData: any,
71
+ RSSI: number
72
+ ) => {
73
+ self.handlePeripheral(peripheral, advertisementData, RSSI);
74
+ },
75
+ });
76
+
77
+ this.centralManager.setDelegate(this.delegate);
78
+
79
+ // 检查当前状态
80
+ const currentState = this.centralManager.state();
81
+ if (currentState === 5) {
82
+ this.initialized = true;
83
+ resolve();
84
+ }
85
+ // 否则等待委托回调
86
+ // #endif
87
+
88
+ // #ifndef APP-PLUS
89
+ reject(createError(ErrorCodes.UNSUPPORTED_PLATFORM, '非 APP 环境不支持原生 iOS 扫描'));
90
+ // #endif
91
+ } catch (error) {
92
+ reject(handlePlatformError(error, 'ios'));
93
+ }
94
+ });
95
+ }
96
+
97
+ protected async startScanInternal(): Promise<void> {
98
+ return new Promise((resolve, reject) => {
99
+ try {
100
+ // #ifdef APP-PLUS
101
+ const CBUUID = plus.ios.importClass('CBUUID');
102
+ const NSArray = plus.ios.importClass('NSArray');
103
+ const NSMutableDictionary = plus.ios.importClass('NSMutableDictionary');
104
+
105
+ // 构建服务 UUID 列表
106
+ let serviceUUIDs: any = null;
107
+ if (this.options.services && this.options.services.length > 0) {
108
+ const uuids: any[] = [];
109
+ this.options.services.forEach((uuid) => {
110
+ const cbUuid = CBUUID.UUIDWithString(uuid);
111
+ uuids.push(cbUuid);
112
+ });
113
+ serviceUUIDs = NSArray.arrayWithArray(uuids);
114
+ }
115
+
116
+ // 构建扫描选项
117
+ const options = NSMutableDictionary.dictionary();
118
+ options.setObjectForKey(
119
+ this.options.allowDuplicates ? 1 : 0,
120
+ 'CBCentralManagerScanOptionAllowDuplicatesKey'
121
+ );
122
+
123
+ // 开始扫描
124
+ this.centralManager.scanForPeripheralsWithServicesOptions(serviceUUIDs, options);
125
+ resolve();
126
+ // #endif
127
+
128
+ // #ifndef APP-PLUS
129
+ reject(Errors.unsupportedPlatform());
130
+ // #endif
131
+ } catch (error) {
132
+ reject(handlePlatformError(error, 'ios'));
133
+ }
134
+ });
135
+ }
136
+
137
+ protected async stopScanInternal(): Promise<void> {
138
+ return new Promise((resolve, reject) => {
139
+ try {
140
+ // #ifdef APP-PLUS
141
+ if (this.centralManager) {
142
+ this.centralManager.stopScan();
143
+ }
144
+ resolve();
145
+ // #endif
146
+
147
+ // #ifndef APP-PLUS
148
+ reject(Errors.unsupportedPlatform());
149
+ // #endif
150
+ } catch (error) {
151
+ reject(handlePlatformError(error, 'ios'));
152
+ }
153
+ });
154
+ }
155
+
156
+ async checkPermissions(): Promise<boolean> {
157
+ // iOS 权限由系统自动管理
158
+ // 需要在原生工程的 Info.plist 中配置 NSBluetoothAlwaysUsageDescription
159
+ return true;
160
+ }
161
+
162
+ async requestPermissions(): Promise<boolean> {
163
+ // iOS 权限由系统自动管理
164
+ return true;
165
+ }
166
+
167
+ onAdapterStateChange(callback: (state: AdapterState) => void): void {
168
+ this.adapterStateListeners.push(callback);
169
+
170
+ // 委托中已处理状态变化
171
+ }
172
+
173
+ destroy(): void {
174
+ super.destroy();
175
+
176
+ // #ifdef APP-PLUS
177
+ if (this.centralManager) {
178
+ this.centralManager.stopScan();
179
+ plus.ios.deleteObject(this.centralManager);
180
+ }
181
+ if (this.delegate) {
182
+ plus.ios.deleteObject(this.delegate);
183
+ }
184
+ // #endif
185
+
186
+ this.centralManager = null;
187
+ this.delegate = null;
188
+ }
189
+
190
+ /**
191
+ * 处理发现的外设
192
+ */
193
+ private handlePeripheral(peripheral: any, advertisementData: any, RSSI: number): void {
194
+ try {
195
+ // #ifdef APP-PLUS
196
+ const deviceId = peripheral.identifier().UUIDString();
197
+ const name = peripheral.name();
198
+
199
+ // 解析广播数据
200
+ const localName = advertisementData.objectForKey('kCBAdvDataLocalName');
201
+ const serviceUUIDs = advertisementData.objectForKey('kCBAdvDataServiceUUIDs');
202
+ const manufacturerData = advertisementData.objectForKey('kCBAdvDataManufacturerData');
203
+ const isConnectable = advertisementData.objectForKey('kCBAdvDataIsConnectable');
204
+ const txPowerLevel = advertisementData.objectForKey('kCBAdvDataTxPowerLevel');
205
+
206
+ // 转换服务 UUID
207
+ const serviceUuids: string[] = [];
208
+ if (serviceUUIDs) {
209
+ const count = serviceUUIDs.count();
210
+ for (let i = 0; i < count; i++) {
211
+ const uuid = serviceUUIDs.objectAtIndex(i).UUIDString();
212
+ serviceUuids.push(uuid);
213
+ }
214
+ }
215
+
216
+ // 转换制造商数据
217
+ let manufacturerDataBuffer: ArrayBuffer | undefined;
218
+ if (manufacturerData) {
219
+ const length = manufacturerData.length();
220
+ const bytes = new Uint8Array(length);
221
+ manufacturerData.getBytes(bytes);
222
+ manufacturerDataBuffer = bytes.buffer;
223
+ }
224
+
225
+ this.handleDeviceFound({
226
+ deviceId,
227
+ name: name || localName,
228
+ RSSI,
229
+ advertisData: manufacturerDataBuffer,
230
+ serviceUuids,
231
+ localName,
232
+ isConnectable,
233
+ txPowerLevel,
234
+ });
235
+ // #endif
236
+ } catch (error) {
237
+ console.error('处理外设数据失败:', error);
238
+ }
239
+ }
240
+
241
+ /**
242
+ * 获取 iOS 蓝牙适配器状态
243
+ */
244
+ async getAdapterState(): Promise<AdapterState> {
245
+ return new Promise((resolve, reject) => {
246
+ // #ifdef APP-PLUS
247
+ try {
248
+ const state = this.centralManager.state();
249
+ // CBManagerStatePoweredOn = 5
250
+ resolve({
251
+ available: state === 5,
252
+ discovering: this.state === 'scanning',
253
+ });
254
+ } catch (error) {
255
+ reject(handlePlatformError(error, 'ios'));
256
+ }
257
+ // #endif
258
+
259
+ // #ifndef APP-PLUS
260
+ reject(Errors.unsupportedPlatform());
261
+ // #endif
262
+ });
263
+ }
264
+ }
@@ -0,0 +1,171 @@
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 } from '../utils/errors';
9
+ import { checkPermissions, requestPermissions } from '../utils/permissions';
10
+
11
+ /**
12
+ * UniApp 蓝牙扫描器
13
+ * 支持安卓、iOS 和鸿蒙平台
14
+ */
15
+ export class UniAppScanner extends BaseScanner {
16
+ private adapterStateChangeHandler: ((res: any) => void) | null = null;
17
+
18
+ getPlatform(): Platform {
19
+ const systemInfo = uni.getSystemInfoSync();
20
+ const platform = systemInfo.platform?.toLowerCase() || '';
21
+
22
+ if (platform === 'android') {
23
+ return Platform.ANDROID;
24
+ } else if (platform === 'ios') {
25
+ return Platform.IOS;
26
+ } else if (platform === 'harmony' || platform === 'harmonyos') {
27
+ return Platform.HARMONY;
28
+ }
29
+
30
+ return Platform.UNKNOWN;
31
+ }
32
+
33
+ async init(): Promise<void> {
34
+ return new Promise((resolve, reject) => {
35
+ // 打开蓝牙适配器
36
+ uni.openBluetoothAdapter({
37
+ success: () => {
38
+ this.initialized = true;
39
+ resolve();
40
+ },
41
+ fail: (err) => {
42
+ reject(createError(
43
+ ErrorCodes.ADAPTER_NOT_AVAILABLE,
44
+ `初始化蓝牙适配器失败: ${err.errMsg}`,
45
+ err.errCode,
46
+ err
47
+ ));
48
+ },
49
+ });
50
+ });
51
+ }
52
+
53
+ protected async startScanInternal(): Promise<void> {
54
+ return new Promise((resolve, reject) => {
55
+ const scanOptions: UniApp.StartBluetoothDevicesDiscoveryOptions = {
56
+ allowDuplicatesKey: this.options.allowDuplicates || false,
57
+ success: () => {
58
+ // 监听设备发现
59
+ this.startDeviceDiscovery();
60
+ resolve();
61
+ },
62
+ fail: (err) => {
63
+ reject(Errors.scanFailed(err.errMsg));
64
+ },
65
+ };
66
+
67
+ // 添加服务UUID过滤
68
+ if (this.options.services && this.options.services.length > 0) {
69
+ scanOptions.services = this.options.services;
70
+ }
71
+
72
+ uni.startBluetoothDevicesDiscovery(scanOptions);
73
+ });
74
+ }
75
+
76
+ protected async stopScanInternal(): Promise<void> {
77
+ return new Promise((resolve, reject) => {
78
+ uni.stopBluetoothDevicesDiscovery({
79
+ success: () => {
80
+ this.stopDeviceDiscovery();
81
+ resolve();
82
+ },
83
+ fail: (err) => {
84
+ reject(Errors.scanFailed(err.errMsg));
85
+ },
86
+ });
87
+ });
88
+ }
89
+
90
+ async checkPermissions(): Promise<boolean> {
91
+ return checkPermissions();
92
+ }
93
+
94
+ async requestPermissions(): Promise<boolean> {
95
+ return requestPermissions();
96
+ }
97
+
98
+ onAdapterStateChange(callback: (state: AdapterState) => void): void {
99
+ this.adapterStateListeners.push(callback);
100
+
101
+ // 如果还没有监听,开始监听
102
+ if (!this.adapterStateChangeHandler) {
103
+ this.adapterStateChangeHandler = (res) => {
104
+ this.emitAdapterStateChange({
105
+ available: res.available,
106
+ discovering: res.discovering,
107
+ });
108
+ };
109
+
110
+ uni.onBluetoothAdapterStateChange(this.adapterStateChangeHandler);
111
+ }
112
+ }
113
+
114
+ destroy(): void {
115
+ super.destroy();
116
+
117
+ // 移除适配器状态监听
118
+ if (this.adapterStateChangeHandler) {
119
+ uni.offBluetoothAdapterStateChange(this.adapterStateChangeHandler);
120
+ this.adapterStateChangeHandler = null;
121
+ }
122
+
123
+ // 关闭蓝牙适配器
124
+ uni.closeBluetoothAdapter({
125
+ success: () => {
126
+ console.log('蓝牙适配器已关闭');
127
+ },
128
+ fail: (err) => {
129
+ console.error('关闭蓝牙适配器失败:', err);
130
+ },
131
+ });
132
+ }
133
+
134
+ /**
135
+ * 开始监听设备发现
136
+ */
137
+ private startDeviceDiscovery(): void {
138
+ uni.onBluetoothDeviceFound((res) => {
139
+ const devices = res.devices || [];
140
+ devices.forEach((device) => {
141
+ this.handleDeviceFound(device);
142
+ });
143
+ });
144
+ }
145
+
146
+ /**
147
+ * 停止监听设备发现
148
+ */
149
+ private stopDeviceDiscovery(): void {
150
+ uni.offBluetoothDeviceFound();
151
+ }
152
+
153
+ /**
154
+ * 获取蓝牙适配器状态
155
+ */
156
+ async getAdapterState(): Promise<AdapterState> {
157
+ return new Promise((resolve, reject) => {
158
+ uni.getBluetoothAdapterState({
159
+ success: (res) => {
160
+ resolve({
161
+ available: res.available,
162
+ discovering: res.discovering,
163
+ });
164
+ },
165
+ fail: (err) => {
166
+ reject(Errors.adapterNotAvailable());
167
+ },
168
+ });
169
+ });
170
+ }
171
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 全局类型声明
3
+ */
4
+
5
+ /// <reference path="./uni-types.d.ts" />
6
+
7
+ // 扩展 Window 接口
8
+ declare interface Window {
9
+ setTimeout(callback: Function, ms?: number): number;
10
+ clearTimeout(timeoutId: number): void;
11
+ setInterval(callback: Function, ms?: number): number;
12
+ clearInterval(intervalId: number): void;
13
+ }
14
+
15
+ // 声明全局 uni 对象
16
+ // @ts-ignore
17
+ declare const uni: UniNamespace.Uni;
18
+
19
+ // @ts-ignore
20
+ declare const plus: PlusNamespace.PlusStatic;
21
+
22
+ // 条件编译标记(用于 IDE 识别)
23
+ declare const __UNI_PLATFORM__: string;
24
+ declare const __VUE_OPTIONS_API__: boolean;
25
+ declare const __VUE_PROD_DEVTOOLS__: boolean;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * UniApp 全局类型声明
3
+ */
4
+
5
+ declare const uni: UniNamespace.Uni;
6
+ declare const plus: PlusNamespace.PlusStatic;
7
+
8
+ declare namespace UniNamespace {
9
+ interface Uni {
10
+ getSystemInfoSync(): SystemInfo;
11
+ openBluetoothAdapter(options: {
12
+ success?: (res: any) => void;
13
+ fail?: (err: any) => void;
14
+ complete?: (res: any) => void;
15
+ }): void;
16
+ closeBluetoothAdapter(options: {
17
+ success?: (res: any) => void;
18
+ fail?: (err: any) => void;
19
+ complete?: (res: any) => void;
20
+ }): void;
21
+ getBluetoothAdapterState(options: {
22
+ success?: (res: {
23
+ available: boolean;
24
+ discovering: boolean;
25
+ }) => void;
26
+ fail?: (err: any) => void;
27
+ complete?: (res: any) => void;
28
+ }): void;
29
+ startBluetoothDevicesDiscovery(options: StartBluetoothDevicesDiscoveryOptions): void;
30
+ stopBluetoothDevicesDiscovery(options: {
31
+ success?: (res: any) => void;
32
+ fail?: (err: any) => void;
33
+ complete?: (res: any) => void;
34
+ }): void;
35
+ onBluetoothDeviceFound(callback: (res: { devices: any[] }) => void): void;
36
+ offBluetoothDeviceFound(callback?: (res: any) => void): void;
37
+ onBluetoothAdapterStateChange(callback: (res: {
38
+ available: boolean;
39
+ discovering: boolean;
40
+ }) => void): void;
41
+ offBluetoothAdapterStateChange(callback?: (res: any) => void): void;
42
+ requestAndroidPermission(
43
+ permission: string,
44
+ success?: (result: { granted: boolean }) => void,
45
+ fail?: (error: any) => void
46
+ ): void;
47
+ showToast(options: {
48
+ title: string;
49
+ icon?: 'success' | 'loading' | 'none';
50
+ duration?: number;
51
+ }): void;
52
+ }
53
+
54
+ interface SystemInfo {
55
+ platform?: string;
56
+ system?: string;
57
+ }
58
+
59
+ interface StartBluetoothDevicesDiscoveryOptions {
60
+ services?: string[];
61
+ allowDuplicatesKey?: boolean;
62
+ interval?: number;
63
+ success?: (res: any) => void;
64
+ fail?: (err: any) => void;
65
+ complete?: (res: any) => void;
66
+ }
67
+ }
68
+
69
+ declare namespace PlusNamespace {
70
+ interface PlusStatic {
71
+ android: {
72
+ runtimeMainActivity(): any;
73
+ importClass(className: string): any;
74
+ };
75
+ ios: {
76
+ importClass(className: string): any;
77
+ implements(protocol: string, methods: Record<string, Function>): any;
78
+ deleteObject(obj: any): void;
79
+ };
80
+ }
81
+ }
82
+
83
+ declare function getContext(obj: any): any;