@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
package/src/types.ts ADDED
@@ -0,0 +1,178 @@
1
+ /**
2
+ * 蓝牙设备信息
3
+ */
4
+ export interface BLEDevice {
5
+ /** 设备ID */
6
+ deviceId: string;
7
+ /** 设备名称 */
8
+ name?: string;
9
+ /** 信号强度 */
10
+ RSSI: number;
11
+ /** 广播数据 */
12
+ advertisData?: ArrayBuffer;
13
+ /** 广播数据 (Base64) */
14
+ advertisDataBase64?: string;
15
+ /** 制造商数据 */
16
+ manufacturerData?: Record<string, ArrayBuffer>;
17
+ /** 服务UUID列表 */
18
+ serviceUuids?: string[];
19
+ /** 本地名称 */
20
+ localName?: string;
21
+ /** 是否可连接 */
22
+ isConnectable?: boolean;
23
+ /** 发射功率 */
24
+ txPowerLevel?: number;
25
+ /** 平台特定数据 */
26
+ platformData?: Record<string, any>;
27
+ }
28
+
29
+ /**
30
+ * 扫描配置选项
31
+ */
32
+ export interface ScanOptions {
33
+ /** 扫描超时时间 (毫秒),默认 10000 */
34
+ timeout?: number;
35
+ /** 是否允许重复上报同一设备 */
36
+ allowDuplicates?: boolean;
37
+ /** 要扫描的服务UUID列表 */
38
+ services?: string[];
39
+ /** 设备名称过滤器 */
40
+ nameFilter?: string | string[];
41
+ /** 信号强度阈值 (dBm),低于此值的设备将被过滤 */
42
+ rssiThreshold?: number;
43
+ /** 制造商数据过滤器 */
44
+ manufacturerFilter?: number | number[];
45
+ }
46
+
47
+ /**
48
+ * 扫描状态
49
+ */
50
+ export enum ScanState {
51
+ IDLE = 'idle',
52
+ SCANNING = 'scanning',
53
+ STOPPING = 'stopping',
54
+ ERROR = 'error'
55
+ }
56
+
57
+ /**
58
+ * 平台类型
59
+ */
60
+ export enum Platform {
61
+ ANDROID = 'android',
62
+ IOS = 'ios',
63
+ HARMONY = 'harmony',
64
+ UNKNOWN = 'unknown'
65
+ }
66
+
67
+ /**
68
+ * 蓝牙适配器状态
69
+ */
70
+ export interface AdapterState {
71
+ /** 是否可用 */
72
+ available: boolean;
73
+ /** 是否已开启 */
74
+ discovering: boolean;
75
+ }
76
+
77
+ /**
78
+ * 扫描事件回调
79
+ */
80
+ export interface ScanCallbacks {
81
+ /** 发现设备回调 */
82
+ onDeviceFound?: (device: BLEDevice) => void;
83
+ /** 扫描开始回调 */
84
+ onScanStart?: () => void;
85
+ /** 扫描停止回调 */
86
+ onScanStop?: () => void;
87
+ /** 扫描超时回调 */
88
+ onTimeout?: () => void;
89
+ /** 错误回调 */
90
+ onError?: (error: BLEError) => void;
91
+ }
92
+
93
+ /**
94
+ * 蓝牙错误
95
+ */
96
+ export interface BLEError {
97
+ /** 错误码 */
98
+ code: number;
99
+ /** 错误信息 */
100
+ message: string;
101
+ /** 平台特定错误码 */
102
+ platformCode?: number;
103
+ /** 原始错误 */
104
+ originalError?: any;
105
+ }
106
+
107
+ /**
108
+ * 蓝牙扫描器接口
109
+ */
110
+ export interface IBLEScanner {
111
+ /** 初始化蓝牙适配器 */
112
+ init(): Promise<void>;
113
+ /** 开始扫描 */
114
+ startScan(options?: ScanOptions, callbacks?: ScanCallbacks): Promise<void>;
115
+ /** 停止扫描 */
116
+ stopScan(): Promise<void>;
117
+ /** 获取已发现的设备列表 */
118
+ getDiscoveredDevices(): BLEDevice[];
119
+ /** 清空设备列表 */
120
+ clearDevices(): void;
121
+ /** 获取当前扫描状态 */
122
+ getState(): ScanState;
123
+ /** 获取当前平台 */
124
+ getPlatform(): Platform;
125
+ /** 检查蓝牙权限 */
126
+ checkPermissions(): Promise<boolean>;
127
+ /** 请求蓝牙权限 */
128
+ requestPermissions(): Promise<boolean>;
129
+ /** 监听适配器状态变化 */
130
+ onAdapterStateChange(callback: (state: AdapterState) => void): void;
131
+ /** 销毁扫描器 */
132
+ destroy(): void;
133
+ }
134
+
135
+ /**
136
+ * 平台特定配置
137
+ */
138
+ export interface PlatformConfig {
139
+ /** 安卓配置 */
140
+ android?: {
141
+ /** 扫描模式 */
142
+ scanMode?: number;
143
+ /** 回调类型 */
144
+ callbackType?: number;
145
+ /** 匹配模式 */
146
+ matchMode?: number;
147
+ /** 是否使用旧版扫描API */
148
+ useLegacyScan?: boolean;
149
+ };
150
+ /** iOS配置 */
151
+ ios?: {
152
+ /** 是否允许后台扫描 */
153
+ allowBackgroundScan?: boolean;
154
+ /** 恢复标识 */
155
+ restoreIdentifier?: string;
156
+ };
157
+ /** 鸿蒙配置 */
158
+ harmony?: {
159
+ /** 扫描过滤器 */
160
+ filter?: any;
161
+ };
162
+ }
163
+
164
+ /**
165
+ * 扫描结果过滤器
166
+ */
167
+ export interface DeviceFilter {
168
+ /** 按名称过滤 */
169
+ name?: string | string[] | RegExp;
170
+ /** 按服务UUID过滤 */
171
+ serviceUuid?: string | string[];
172
+ /** 按制造商ID过滤 */
173
+ manufacturerId?: number | number[];
174
+ /** 按信号强度过滤 */
175
+ minRssi?: number;
176
+ /** 自定义过滤函数 */
177
+ custom?: (device: BLEDevice) => boolean;
178
+ }
@@ -0,0 +1,217 @@
1
+ import { BLEDevice } from '../types';
2
+
3
+ /**
4
+ * 解析广播数据
5
+ */
6
+ export function parseAdvertisementData(data: ArrayBuffer | string): {
7
+ manufacturerData?: Record<string, ArrayBuffer>;
8
+ serviceUuids?: string[];
9
+ localName?: string;
10
+ txPowerLevel?: number;
11
+ isConnectable?: boolean;
12
+ } {
13
+ const result: ReturnType<typeof parseAdvertisementData> = {};
14
+
15
+ if (!data) {
16
+ return result;
17
+ }
18
+
19
+ let buffer: ArrayBuffer;
20
+ if (typeof data === 'string') {
21
+ // Base64 解码
22
+ buffer = base64ToArrayBuffer(data);
23
+ } else {
24
+ buffer = data;
25
+ }
26
+
27
+ const view = new DataView(buffer);
28
+ let offset = 0;
29
+
30
+ const manufacturerData: Record<string, ArrayBuffer> = {};
31
+ const serviceUuids: string[] = [];
32
+
33
+ while (offset < view.byteLength) {
34
+ const length = view.getUint8(offset);
35
+ if (length === 0) break;
36
+
37
+ const type = view.getUint8(offset + 1);
38
+ const dataOffset = offset + 2;
39
+ const dataLength = length - 1;
40
+
41
+ switch (type) {
42
+ case 0x01: // Flags
43
+ const flags = view.getUint8(dataOffset);
44
+ result.isConnectable = (flags & 0x02) !== 0;
45
+ break;
46
+
47
+ case 0x02: // 16-bit Service UUIDs (partial)
48
+ case 0x03: // 16-bit Service UUIDs (complete)
49
+ for (let i = 0; i < dataLength; i += 2) {
50
+ const uuid = view.getUint16(dataOffset + i, true).toString(16).padStart(4, '0');
51
+ serviceUuids.push(`0000${uuid}-0000-1000-8000-00805f9b34fb`);
52
+ }
53
+ break;
54
+
55
+ case 0x04: // 32-bit Service UUIDs (partial)
56
+ case 0x05: // 32-bit Service UUIDs (complete)
57
+ for (let i = 0; i < dataLength; i += 4) {
58
+ const uuid = view.getUint32(dataOffset + i, true).toString(16).padStart(8, '0');
59
+ serviceUuids.push(`${uuid}-0000-1000-8000-00805f9b34fb`);
60
+ }
61
+ break;
62
+
63
+ case 0x06: // 128-bit Service UUIDs (partial)
64
+ case 0x07: // 128-bit Service UUIDs (complete)
65
+ for (let i = 0; i < dataLength; i += 16) {
66
+ const uuid = parseUUID(buffer.slice(dataOffset + i, dataOffset + i + 16));
67
+ serviceUuids.push(uuid);
68
+ }
69
+ break;
70
+
71
+ case 0x08: // Shortened Local Name
72
+ case 0x09: // Complete Local Name
73
+ result.localName = decodeUTF8(buffer.slice(dataOffset, dataOffset + dataLength));
74
+ break;
75
+
76
+ case 0x0A: // TX Power Level
77
+ result.txPowerLevel = view.getInt8(dataOffset);
78
+ break;
79
+
80
+ case 0xFF: // Manufacturer Specific Data
81
+ const companyId = view.getUint16(dataOffset, true);
82
+ const companyIdHex = companyId.toString(16).padStart(4, '0');
83
+ manufacturerData[companyIdHex] = buffer.slice(dataOffset + 2, dataOffset + dataLength);
84
+ break;
85
+ }
86
+
87
+ offset += length + 1;
88
+ }
89
+
90
+ if (Object.keys(manufacturerData).length > 0) {
91
+ result.manufacturerData = manufacturerData;
92
+ }
93
+ if (serviceUuids.length > 0) {
94
+ result.serviceUuids = serviceUuids;
95
+ }
96
+
97
+ return result;
98
+ }
99
+
100
+ /**
101
+ * Base64 转 ArrayBuffer
102
+ */
103
+ export function base64ToArrayBuffer(base64: string): ArrayBuffer {
104
+ const binary = atob(base64);
105
+ const buffer = new ArrayBuffer(binary.length);
106
+ const view = new Uint8Array(buffer);
107
+ for (let i = 0; i < binary.length; i++) {
108
+ view[i] = binary.charCodeAt(i);
109
+ }
110
+ return buffer;
111
+ }
112
+
113
+ /**
114
+ * ArrayBuffer 转 Base64
115
+ */
116
+ export function arrayBufferToBase64(buffer: ArrayBuffer): string {
117
+ const bytes = new Uint8Array(buffer);
118
+ let binary = '';
119
+ for (let i = 0; i < bytes.byteLength; i++) {
120
+ binary += String.fromCharCode(bytes[i]);
121
+ }
122
+ return btoa(binary);
123
+ }
124
+
125
+ /**
126
+ * 解析 UUID
127
+ */
128
+ function parseUUID(buffer: ArrayBuffer): string {
129
+ const view = new DataView(buffer);
130
+ const parts = [
131
+ view.getUint32(0, false).toString(16).padStart(8, '0'),
132
+ view.getUint16(4, false).toString(16).padStart(4, '0'),
133
+ view.getUint16(6, false).toString(16).padStart(4, '0'),
134
+ view.getUint16(8, false).toString(16).padStart(4, '0'),
135
+ view.getUint32(10, false).toString(16).padStart(8, '0') +
136
+ view.getUint16(14, false).toString(16).padStart(4, '0'),
137
+ ];
138
+ return `${parts[0]}-${parts[1]}-${parts[2]}-${parts[3]}-${parts[4]}`;
139
+ }
140
+
141
+ /**
142
+ * UTF-8 解码
143
+ */
144
+ function decodeUTF8(buffer: ArrayBuffer): string {
145
+ const decoder = new TextDecoder('utf-8');
146
+ return decoder.decode(buffer);
147
+ }
148
+
149
+ /**
150
+ * 标准化设备数据
151
+ */
152
+ export function normalizeDeviceData(device: any): BLEDevice {
153
+ const normalized: BLEDevice = {
154
+ deviceId: device.deviceId || '',
155
+ name: device.name || device.localName,
156
+ RSSI: device.RSSI || device.rssi || -100,
157
+ };
158
+
159
+ // 处理广播数据
160
+ if (device.advertisData) {
161
+ normalized.advertisData = device.advertisData;
162
+ if (typeof device.advertisData === 'string') {
163
+ normalized.advertisDataBase64 = device.advertisData;
164
+ } else {
165
+ normalized.advertisDataBase64 = arrayBufferToBase64(device.advertisData);
166
+ }
167
+
168
+ // 解析广播数据
169
+ const parsed = parseAdvertisementData(device.advertisData);
170
+ Object.assign(normalized, parsed);
171
+ }
172
+
173
+ // 处理制造商数据
174
+ if (device.manufacturerData) {
175
+ normalized.manufacturerData = device.manufacturerData;
176
+ }
177
+
178
+ // 处理服务UUID
179
+ if (device.serviceUuids) {
180
+ normalized.serviceUuids = device.serviceUuids;
181
+ }
182
+
183
+ // 平台特定数据
184
+ normalized.platformData = {
185
+ raw: device,
186
+ };
187
+
188
+ return normalized;
189
+ }
190
+
191
+ /**
192
+ * 格式化 UUID (统一格式)
193
+ */
194
+ export function formatUUID(uuid: string): string {
195
+ // 移除所有非十六进制字符
196
+ const clean = uuid.toLowerCase().replace(/[^0-9a-f]/g, '');
197
+
198
+ if (clean.length === 4) {
199
+ // 16-bit UUID
200
+ return `0000${clean}-0000-1000-8000-00805f9b34fb`;
201
+ } else if (clean.length === 8) {
202
+ // 32-bit UUID
203
+ return `${clean}-0000-1000-8000-00805f9b34fb`;
204
+ } else if (clean.length === 32) {
205
+ // 128-bit UUID
206
+ return `${clean.slice(0, 8)}-${clean.slice(8, 12)}-${clean.slice(12, 16)}-${clean.slice(16, 20)}-${clean.slice(20)}`;
207
+ }
208
+
209
+ return uuid;
210
+ }
211
+
212
+ /**
213
+ * 检查 UUID 是否匹配
214
+ */
215
+ export function matchUUID(uuid1: string, uuid2: string): boolean {
216
+ return formatUUID(uuid1) === formatUUID(uuid2);
217
+ }
@@ -0,0 +1,105 @@
1
+ import { BLEError } from '../types';
2
+
3
+ /**
4
+ * 错误码定义
5
+ */
6
+ export const ErrorCodes: Record<string, number> = {
7
+ // 通用错误
8
+ UNKNOWN_ERROR: 1000,
9
+ NOT_INITIALIZED: 1001,
10
+ ALREADY_SCANNING: 1002,
11
+ NOT_SCANNING: 1003,
12
+ PERMISSION_DENIED: 1004,
13
+ BLUETOOTH_DISABLED: 1005,
14
+ UNSUPPORTED_PLATFORM: 1006,
15
+
16
+ // 蓝牙特定错误
17
+ ADAPTER_NOT_AVAILABLE: 2001,
18
+ SCAN_FAILED: 2002,
19
+ DEVICE_NOT_FOUND: 2003,
20
+ CONNECTION_FAILED: 2004,
21
+
22
+ // 平台特定错误
23
+ ANDROID_ERROR: 3000,
24
+ IOS_ERROR: 4000,
25
+ HARMONY_ERROR: 5000,
26
+ };
27
+
28
+ /**
29
+ * 创建错误对象
30
+ */
31
+ export function createError(
32
+ code: number,
33
+ message: string,
34
+ platformCode?: number,
35
+ originalError?: any
36
+ ): BLEError {
37
+ return {
38
+ code,
39
+ message,
40
+ platformCode,
41
+ originalError
42
+ };
43
+ }
44
+
45
+ /**
46
+ * 通用错误
47
+ */
48
+ export const Errors = {
49
+ notInitialized: () => createError(
50
+ ErrorCodes.NOT_INITIALIZED,
51
+ '蓝牙适配器未初始化,请先调用 init()'
52
+ ),
53
+ alreadyScanning: () => createError(
54
+ ErrorCodes.ALREADY_SCANNING,
55
+ '扫描已在进行中'
56
+ ),
57
+ notScanning: () => createError(
58
+ ErrorCodes.NOT_SCANNING,
59
+ '扫描未在进行中'
60
+ ),
61
+ permissionDenied: () => createError(
62
+ ErrorCodes.PERMISSION_DENIED,
63
+ '蓝牙权限被拒绝'
64
+ ),
65
+ bluetoothDisabled: () => createError(
66
+ ErrorCodes.BLUETOOTH_DISABLED,
67
+ '蓝牙未开启'
68
+ ),
69
+ unsupportedPlatform: () => createError(
70
+ ErrorCodes.UNSUPPORTED_PLATFORM,
71
+ '当前平台不支持蓝牙功能'
72
+ ),
73
+ adapterNotAvailable: () => createError(
74
+ ErrorCodes.ADAPTER_NOT_AVAILABLE,
75
+ '蓝牙适配器不可用'
76
+ ),
77
+ scanFailed: (reason?: string) => createError(
78
+ ErrorCodes.SCAN_FAILED,
79
+ `扫描失败: ${reason || '未知原因'}`
80
+ ),
81
+ };
82
+
83
+ /**
84
+ * 处理平台特定错误
85
+ */
86
+ export function handlePlatformError(error: any, platform: string): BLEError {
87
+ const platformCode = error?.code || error?.errCode;
88
+ const message = error?.message || error?.errMsg || '未知错误';
89
+
90
+ let code = ErrorCodes.UNKNOWN_ERROR;
91
+
92
+ switch (platform) {
93
+ case 'android':
94
+ code = ErrorCodes.ANDROID_ERROR;
95
+ break;
96
+ case 'ios':
97
+ code = ErrorCodes.IOS_ERROR;
98
+ break;
99
+ case 'harmony':
100
+ code = ErrorCodes.HARMONY_ERROR;
101
+ break;
102
+ }
103
+
104
+ return createError(code, `${platform}平台错误: ${message}`, platformCode, error);
105
+ }