taro-bluetooth-print 2.5.0 → 2.6.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.
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/logo.svg +17 -0
- package/dist/manifest.webmanifest +17 -0
- package/dist/types/adapters/WebBluetoothAdapter.d.ts +87 -1
- package/dist/types/adapters/index.d.ts +1 -1
- package/dist/types/barcode/BarcodeGenerator.d.ts +61 -4
- package/dist/types/barcode/index.d.ts +1 -1
- package/dist/types/template/TemplateEngine.d.ts +140 -1
- package/dist/types/utils/uuid.d.ts +191 -0
- package/package.json +1 -1
- package/src/adapters/ReactNativeAdapter.ts +19 -30
- package/src/adapters/WebBluetoothAdapter.ts +275 -14
- package/src/adapters/index.ts +6 -1
- package/src/barcode/BarcodeGenerator.ts +312 -6
- package/src/barcode/index.ts +1 -1
- package/src/drivers/StarPrinter.ts +14 -13
- package/src/encoding/EncodingService.ts +13 -6
- package/src/encoding/korean-japanese.ts +84 -48
- package/src/services/BatchPrintManager.ts +15 -16
- package/src/services/PrintStatistics.ts +8 -8
- package/src/services/ScheduledRetryManager.ts +15 -19
- package/src/template/TemplateEngine.ts +543 -4
- package/src/utils/image.ts +92 -43
- package/src/utils/platform.ts +28 -14
- package/src/utils/uuid.ts +522 -0
- package/src/utils/validation.ts +1155 -0
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
// Platform type - React Native availability is checked at runtime via Platform.OS
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
interface PlatformInterface {
|
|
11
|
+
OS: string;
|
|
12
|
+
select<T>(options: { ios?: T; android?: T; default?: T }): T;
|
|
13
|
+
}
|
|
14
|
+
const Platform = (globalThis as { Platform?: PlatformInterface }).Platform;
|
|
12
15
|
|
|
13
|
-
import {
|
|
14
|
-
BaseAdapter,
|
|
15
|
-
} from './BaseAdapter';
|
|
16
|
+
import { BaseAdapter } from './BaseAdapter';
|
|
16
17
|
import { IPrinterAdapter, IAdapterOptions, PrinterState } from '@/types';
|
|
17
18
|
import { Logger } from '@/utils/logger';
|
|
18
19
|
import { BluetoothPrintError, ErrorCode } from '@/errors/BluetoothError';
|
|
@@ -48,17 +49,9 @@ interface BLEManager {
|
|
|
48
49
|
onDeviceScanned: (error: unknown, device: unknown) => void
|
|
49
50
|
): void;
|
|
50
51
|
stopDeviceScan(): void;
|
|
51
|
-
connectToDevice(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
): Promise<unknown>;
|
|
55
|
-
disconnectFromDevice(
|
|
56
|
-
deviceIdentifier: string,
|
|
57
|
-
force?: boolean
|
|
58
|
-
): Promise<unknown>;
|
|
59
|
-
discoverAllServicesAndCharacteristicsForDevice(
|
|
60
|
-
deviceIdentifier: string
|
|
61
|
-
): Promise<unknown>;
|
|
52
|
+
connectToDevice(deviceIdentifier: string, options: Record<string, unknown>): Promise<unknown>;
|
|
53
|
+
disconnectFromDevice(deviceIdentifier: string, force?: boolean): Promise<unknown>;
|
|
54
|
+
discoverAllServicesAndCharacteristicsForDevice(deviceIdentifier: string): Promise<unknown>;
|
|
62
55
|
writeCharacteristicWithResponseForDevice(
|
|
63
56
|
deviceIdentifier: string,
|
|
64
57
|
serviceUUID: string,
|
|
@@ -137,14 +130,14 @@ export class ReactNativeAdapter extends BaseAdapter implements IPrinterAdapter {
|
|
|
137
130
|
if (!options?.bleManager) {
|
|
138
131
|
throw new Error(
|
|
139
132
|
'ReactNativeAdapter requires a bleManager instance (e.g., react-native-ble-plx). ' +
|
|
140
|
-
|
|
133
|
+
'Please pass { bleManager: yourBleManager } in the constructor.'
|
|
141
134
|
);
|
|
142
135
|
}
|
|
143
136
|
|
|
144
137
|
this.bleManager = options.bleManager;
|
|
145
138
|
|
|
146
139
|
// Validate platform
|
|
147
|
-
if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
|
|
140
|
+
if (Platform && Platform.OS !== 'ios' && Platform.OS !== 'android') {
|
|
148
141
|
Logger.scope('ReactNativeAdapter').warn(
|
|
149
142
|
`Running on unsupported platform: ${Platform.OS}. BLE may not work correctly.`
|
|
150
143
|
);
|
|
@@ -422,18 +415,15 @@ export class ReactNativeAdapter extends BaseAdapter implements IPrinterAdapter {
|
|
|
422
415
|
startDiscovery?(): Promise<void> {
|
|
423
416
|
return new Promise((resolve, reject) => {
|
|
424
417
|
try {
|
|
425
|
-
this.bleManager.startDeviceScan(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if (error) {
|
|
430
|
-
reject(error);
|
|
431
|
-
}
|
|
418
|
+
this.bleManager.startDeviceScan(null, { allowDuplicates: false }, (error: unknown) => {
|
|
419
|
+
if (error) {
|
|
420
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
421
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
432
422
|
}
|
|
433
|
-
);
|
|
423
|
+
});
|
|
434
424
|
resolve();
|
|
435
425
|
} catch (error) {
|
|
436
|
-
reject(error);
|
|
426
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
437
427
|
}
|
|
438
428
|
});
|
|
439
429
|
}
|
|
@@ -442,7 +432,7 @@ export class ReactNativeAdapter extends BaseAdapter implements IPrinterAdapter {
|
|
|
442
432
|
* Stop discovering nearby Bluetooth devices
|
|
443
433
|
*/
|
|
444
434
|
stopDiscovery?(): Promise<void> {
|
|
445
|
-
return new Promise(
|
|
435
|
+
return new Promise(resolve => {
|
|
446
436
|
this.bleManager.stopDeviceScan();
|
|
447
437
|
resolve();
|
|
448
438
|
});
|
|
@@ -466,8 +456,7 @@ export class ReactNativeAdapter extends BaseAdapter implements IPrinterAdapter {
|
|
|
466
456
|
|
|
467
457
|
for (const service of services) {
|
|
468
458
|
const writeChar = service.characteristics.find(
|
|
469
|
-
(c: RNCharacteristic) =>
|
|
470
|
-
c.isWritableWithResponse || c.isWritableWithoutResponse
|
|
459
|
+
(c: RNCharacteristic) => c.isWritableWithResponse || c.isWritableWithoutResponse
|
|
471
460
|
);
|
|
472
461
|
|
|
473
462
|
if (writeChar) {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Web Bluetooth Adapter
|
|
3
3
|
* Implements the IPrinterAdapter interface for Web Bluetooth API (H5 environment)
|
|
4
|
+
*
|
|
5
|
+
* Provides enhanced device filtering, RSSI signal strength monitoring,
|
|
6
|
+
* and improved disconnection handling.
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
9
|
import { IAdapterOptions, PrinterState } from '@/types';
|
|
@@ -14,6 +17,12 @@ interface WebBluetoothDeviceInfo {
|
|
|
14
17
|
device: BluetoothDevice;
|
|
15
18
|
server: BluetoothRemoteGATTServer;
|
|
16
19
|
characteristic: BluetoothRemoteGATTCharacteristic;
|
|
20
|
+
/** RSSI value at time of connection (if available) */
|
|
21
|
+
rssiAtConnection?: number;
|
|
22
|
+
/** Device discovered timestamp */
|
|
23
|
+
discoveredAt?: number;
|
|
24
|
+
/** Device name for reference */
|
|
25
|
+
name?: string;
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
/**
|
|
@@ -28,6 +37,49 @@ export interface WebBluetoothRequestOptions {
|
|
|
28
37
|
acceptAllDevices?: boolean;
|
|
29
38
|
/** Optional services to access */
|
|
30
39
|
optionalServices?: string[];
|
|
40
|
+
/** Minimum RSSI signal strength (dBm) to accept device */
|
|
41
|
+
minRSSI?: number;
|
|
42
|
+
/** Filter by device name (exact or partial match) */
|
|
43
|
+
name?: string;
|
|
44
|
+
/** Filter by manufacturer data patterns */
|
|
45
|
+
manufacturerDataFilter?: Array<{
|
|
46
|
+
companyIdentifier: number;
|
|
47
|
+
dataPrefix?: Uint8Array;
|
|
48
|
+
}>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Discovered device information
|
|
53
|
+
*/
|
|
54
|
+
export interface DiscoveredDevice {
|
|
55
|
+
/** Device instance */
|
|
56
|
+
device: BluetoothDevice;
|
|
57
|
+
/** Device name */
|
|
58
|
+
name: string;
|
|
59
|
+
/** Device ID */
|
|
60
|
+
deviceId: string;
|
|
61
|
+
/** RSSI signal strength (dBm) */
|
|
62
|
+
rssi?: number;
|
|
63
|
+
/** Timestamp when device was discovered */
|
|
64
|
+
discoveredAt: number;
|
|
65
|
+
/** Manufacturer data if available */
|
|
66
|
+
manufacturerData?: Map<number, Uint8Array>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Device filter options for scanning
|
|
71
|
+
*/
|
|
72
|
+
export interface DeviceFilterOptions {
|
|
73
|
+
/** Minimum RSSI threshold (dBm) */
|
|
74
|
+
minRSSI?: number;
|
|
75
|
+
/** Maximum RSSI threshold (dBm) */
|
|
76
|
+
maxRSSI?: number;
|
|
77
|
+
/** Name prefix filter */
|
|
78
|
+
namePrefix?: string;
|
|
79
|
+
/** Name exact match filter */
|
|
80
|
+
name?: string;
|
|
81
|
+
/** Service UUIDs to filter */
|
|
82
|
+
serviceUUIDs?: string[];
|
|
31
83
|
}
|
|
32
84
|
|
|
33
85
|
/**
|
|
@@ -35,8 +87,8 @@ export interface WebBluetoothRequestOptions {
|
|
|
35
87
|
*/
|
|
36
88
|
const PRINTER_SERVICE_UUIDS = [
|
|
37
89
|
'000018f0-0000-1000-8000-00805f9b34fb', // Common printer service
|
|
38
|
-
'49535343-fe7d-4ae5-8fa9-9fafd205e455', //
|
|
39
|
-
'e7810a71-73ae-499d-8c15-faa9aef0c3f2', //
|
|
90
|
+
'49535343-fe7d-4ae5-8fa9-9fafd205e455', // Serial Port Profile
|
|
91
|
+
'e7810a71-73ae-499d-8c15-faa9aef0c3f2', // Nordic UART Service
|
|
40
92
|
];
|
|
41
93
|
|
|
42
94
|
/**
|
|
@@ -55,6 +107,8 @@ const PRINTER_SERVICE_UUIDS = [
|
|
|
55
107
|
*/
|
|
56
108
|
export class WebBluetoothAdapter extends BaseAdapter {
|
|
57
109
|
private devices: Map<string, WebBluetoothDeviceInfo> = new Map();
|
|
110
|
+
private discoveredDevices: Map<string, DiscoveredDevice> = new Map();
|
|
111
|
+
private connectionCleanupTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
58
112
|
|
|
59
113
|
/**
|
|
60
114
|
* Check if Web Bluetooth API is supported in the current browser
|
|
@@ -125,9 +179,14 @@ export class WebBluetoothAdapter extends BaseAdapter {
|
|
|
125
179
|
|
|
126
180
|
// Check if already connected
|
|
127
181
|
if (this.devices.has(deviceId)) {
|
|
128
|
-
this.
|
|
129
|
-
|
|
130
|
-
|
|
182
|
+
const existingInfo = this.devices.get(deviceId);
|
|
183
|
+
if (existingInfo?.server.connected) {
|
|
184
|
+
this.logger.warn('Device already connected:', deviceId);
|
|
185
|
+
this.updateState(PrinterState.CONNECTED);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
// If not connected but still in map, clean up first
|
|
189
|
+
this.cleanupDeviceInfo(deviceId);
|
|
131
190
|
}
|
|
132
191
|
|
|
133
192
|
this.updateState(PrinterState.CONNECTING);
|
|
@@ -148,8 +207,29 @@ export class WebBluetoothAdapter extends BaseAdapter {
|
|
|
148
207
|
// Discover services and find writeable characteristic
|
|
149
208
|
const characteristic = await this.discoverWriteableCharacteristic(server);
|
|
150
209
|
|
|
210
|
+
// Get RSSI if available (may not be available on all devices)
|
|
211
|
+
let rssi: number | undefined;
|
|
212
|
+
try {
|
|
213
|
+
if ('readRemoteRssi' in characteristic.service.device) {
|
|
214
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
215
|
+
rssi = await (characteristic.service.device as any).readRemoteRssi();
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
this.logger.debug('RSSI reading not supported on this device');
|
|
219
|
+
}
|
|
220
|
+
|
|
151
221
|
// Store device info
|
|
152
|
-
|
|
222
|
+
const deviceInfo: WebBluetoothDeviceInfo = {
|
|
223
|
+
device,
|
|
224
|
+
server,
|
|
225
|
+
characteristic,
|
|
226
|
+
discoveredAt: Date.now(),
|
|
227
|
+
};
|
|
228
|
+
if (rssi !== undefined) {
|
|
229
|
+
deviceInfo.rssiAtConnection = rssi;
|
|
230
|
+
}
|
|
231
|
+
this.devices.set(deviceId, deviceInfo);
|
|
232
|
+
|
|
153
233
|
this.serviceCache.set(deviceId, {
|
|
154
234
|
serviceId: characteristic.service?.uuid || '',
|
|
155
235
|
characteristicId: characteristic.uuid,
|
|
@@ -189,28 +269,52 @@ export class WebBluetoothAdapter extends BaseAdapter {
|
|
|
189
269
|
|
|
190
270
|
/**
|
|
191
271
|
* Disconnect from a Bluetooth device
|
|
272
|
+
* Enhanced to properly clean up all resources and event listeners
|
|
192
273
|
*
|
|
193
274
|
* @param deviceId - Bluetooth device ID
|
|
275
|
+
* @param force - If true, force disconnection even if device not found in cache
|
|
194
276
|
*/
|
|
195
|
-
disconnect(deviceId: string):
|
|
277
|
+
disconnect(deviceId: string, force = false): void {
|
|
196
278
|
this.validateDeviceId(deviceId);
|
|
279
|
+
|
|
280
|
+
const deviceInfo = this.devices.get(deviceId);
|
|
281
|
+
|
|
282
|
+
// If device not found and not forcing, just return
|
|
283
|
+
if (!deviceInfo && !force) {
|
|
284
|
+
this.logger.debug('Device not found in cache, nothing to disconnect');
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
197
288
|
this.updateState(PrinterState.DISCONNECTING);
|
|
198
289
|
this.logger.debug('Disconnecting from device:', deviceId);
|
|
199
290
|
|
|
200
291
|
try {
|
|
201
|
-
|
|
292
|
+
// Cancel any pending connection cleanup
|
|
293
|
+
if (this.connectionCleanupTimeout) {
|
|
294
|
+
clearTimeout(this.connectionCleanupTimeout);
|
|
295
|
+
this.connectionCleanupTimeout = null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Disconnect from GATT server
|
|
202
299
|
if (deviceInfo?.server?.connected) {
|
|
203
300
|
deviceInfo.server.disconnect();
|
|
301
|
+
this.logger.debug('GATT server disconnected');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Remove thegattserverdisconnected event listener to prevent double-handling
|
|
305
|
+
if (deviceInfo?.device) {
|
|
306
|
+
deviceInfo.device.removeEventListener('gattserverdisconnected', () => {
|
|
307
|
+
this.handleDisconnection(deviceId);
|
|
308
|
+
});
|
|
204
309
|
}
|
|
205
310
|
} catch (error) {
|
|
206
|
-
this.logger.warn('Disconnect error
|
|
311
|
+
this.logger.warn('Disconnect error:', error);
|
|
207
312
|
} finally {
|
|
313
|
+
// Always cleanup resources
|
|
208
314
|
this.cleanupDeviceInfo(deviceId);
|
|
209
315
|
this.updateState(PrinterState.DISCONNECTED);
|
|
210
316
|
this.logger.info('Device disconnected successfully');
|
|
211
317
|
}
|
|
212
|
-
|
|
213
|
-
return Promise.resolve();
|
|
214
318
|
}
|
|
215
319
|
|
|
216
320
|
/**
|
|
@@ -290,10 +394,126 @@ export class WebBluetoothAdapter extends BaseAdapter {
|
|
|
290
394
|
this.logger.info(`Successfully wrote ${data.length} bytes`);
|
|
291
395
|
}
|
|
292
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Get device ID from a BluetoothDevice instance
|
|
399
|
+
* Handles different browser implementations
|
|
400
|
+
*
|
|
401
|
+
* @param device - BluetoothDevice instance
|
|
402
|
+
* @returns Device ID string
|
|
403
|
+
*/
|
|
404
|
+
getDeviceId(device: BluetoothDevice): string {
|
|
405
|
+
if (!device) {
|
|
406
|
+
throw new BluetoothPrintError(ErrorCode.DEVICE_NOT_FOUND, 'Device is required');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Use device.id if available, otherwise fall back to generated ID
|
|
410
|
+
const deviceId = device.id || this.generateFallbackDeviceId(device);
|
|
411
|
+
|
|
412
|
+
return deviceId;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Get device information including RSSI
|
|
417
|
+
*
|
|
418
|
+
* @param deviceId - Bluetooth device ID
|
|
419
|
+
* @returns Device info object with RSSI and metadata
|
|
420
|
+
*/
|
|
421
|
+
getDeviceInfo(
|
|
422
|
+
deviceId: string
|
|
423
|
+
): { deviceId: string; name: string; rssi?: number; connected: boolean } | null {
|
|
424
|
+
const deviceInfo = this.devices.get(deviceId);
|
|
425
|
+
|
|
426
|
+
if (!deviceInfo) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const result: { deviceId: string; name: string; rssi?: number; connected: boolean } = {
|
|
431
|
+
deviceId,
|
|
432
|
+
name: deviceInfo.device.name || 'Unknown Device',
|
|
433
|
+
connected: deviceInfo.server.connected,
|
|
434
|
+
};
|
|
435
|
+
if (deviceInfo.rssiAtConnection !== undefined) {
|
|
436
|
+
result.rssi = deviceInfo.rssiAtConnection;
|
|
437
|
+
}
|
|
438
|
+
return result;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Filter discovered devices by criteria
|
|
443
|
+
*
|
|
444
|
+
* @param devices - Array of discovered devices
|
|
445
|
+
* @param filter - Filter criteria
|
|
446
|
+
* @returns Filtered array of devices
|
|
447
|
+
*/
|
|
448
|
+
filterDevices(devices: DiscoveredDevice[], filter: DeviceFilterOptions): DiscoveredDevice[] {
|
|
449
|
+
return devices.filter(device => {
|
|
450
|
+
// RSSI filtering
|
|
451
|
+
if (
|
|
452
|
+
filter.minRSSI !== undefined &&
|
|
453
|
+
(device.rssi === undefined || device.rssi < filter.minRSSI)
|
|
454
|
+
) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
if (
|
|
458
|
+
filter.maxRSSI !== undefined &&
|
|
459
|
+
(device.rssi === undefined || device.rssi > filter.maxRSSI)
|
|
460
|
+
) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Name prefix filtering
|
|
465
|
+
if (
|
|
466
|
+
filter.namePrefix &&
|
|
467
|
+
!device.name.toLowerCase().startsWith(filter.namePrefix.toLowerCase())
|
|
468
|
+
) {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Name exact/partial matching
|
|
473
|
+
if (filter.name) {
|
|
474
|
+
const searchName = filter.name.toLowerCase();
|
|
475
|
+
if (!device.name.toLowerCase().includes(searchName)) {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Service UUID filtering
|
|
481
|
+
if (filter.serviceUUIDs && filter.serviceUUIDs.length > 0) {
|
|
482
|
+
const deviceWithUuids = device.device as BluetoothDevice & { uuids?: string[] };
|
|
483
|
+
const deviceServices = Array.from(deviceWithUuids.uuids || []);
|
|
484
|
+
const hasMatchingService = filter.serviceUUIDs.some(uuid =>
|
|
485
|
+
deviceServices.some(deviceUuid => deviceUuid.toLowerCase() === uuid.toLowerCase())
|
|
486
|
+
);
|
|
487
|
+
if (!hasMatchingService) {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return true;
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Sort devices by signal strength (RSSI)
|
|
498
|
+
*
|
|
499
|
+
* @param devices - Array of discovered devices
|
|
500
|
+
* @param ascending - Sort in ascending order (weakest first), default false (strongest first)
|
|
501
|
+
* @returns Sorted array
|
|
502
|
+
*/
|
|
503
|
+
sortByRSSI(devices: DiscoveredDevice[], ascending = false): DiscoveredDevice[] {
|
|
504
|
+
return [...devices].sort((a, b) => {
|
|
505
|
+
const rssiA = a.rssi ?? -Infinity;
|
|
506
|
+
const rssiB = b.rssi ?? -Infinity;
|
|
507
|
+
return ascending ? rssiA - rssiB : rssiB - rssiA;
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
293
511
|
/**
|
|
294
512
|
* Build request options for navigator.bluetooth.requestDevice
|
|
295
513
|
*/
|
|
296
514
|
private buildRequestOptions(options?: WebBluetoothRequestOptions): RequestDeviceOptions {
|
|
515
|
+
const filters: BluetoothLEScanFilter[] = [];
|
|
516
|
+
|
|
297
517
|
if (options?.acceptAllDevices) {
|
|
298
518
|
return {
|
|
299
519
|
acceptAllDevices: true,
|
|
@@ -301,8 +521,7 @@ export class WebBluetoothAdapter extends BaseAdapter {
|
|
|
301
521
|
};
|
|
302
522
|
}
|
|
303
523
|
|
|
304
|
-
|
|
305
|
-
|
|
524
|
+
// Build filters
|
|
306
525
|
if (options?.serviceUUIDs?.length) {
|
|
307
526
|
filters.push({ services: options.serviceUUIDs });
|
|
308
527
|
}
|
|
@@ -311,11 +530,30 @@ export class WebBluetoothAdapter extends BaseAdapter {
|
|
|
311
530
|
filters.push({ namePrefix: options.namePrefix });
|
|
312
531
|
}
|
|
313
532
|
|
|
533
|
+
if (options?.name) {
|
|
534
|
+
filters.push({ name: options.name });
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Manufacturer data filter (if provided)
|
|
538
|
+
// Note: This is a newer API, may not be supported in all browsers
|
|
539
|
+
if (options?.manufacturerDataFilter?.length) {
|
|
540
|
+
for (const mf of options.manufacturerDataFilter) {
|
|
541
|
+
const filter: BluetoothLEScanFilter = {
|
|
542
|
+
manufacturerData: [
|
|
543
|
+
{
|
|
544
|
+
companyIdentifier: mf.companyIdentifier,
|
|
545
|
+
},
|
|
546
|
+
],
|
|
547
|
+
};
|
|
548
|
+
filters.push(filter);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
314
552
|
// Default: filter by common printer services
|
|
315
553
|
if (filters.length === 0) {
|
|
316
554
|
return {
|
|
317
555
|
acceptAllDevices: true,
|
|
318
|
-
optionalServices: PRINTER_SERVICE_UUIDS,
|
|
556
|
+
optionalServices: options?.optionalServices || PRINTER_SERVICE_UUIDS,
|
|
319
557
|
};
|
|
320
558
|
}
|
|
321
559
|
|
|
@@ -335,6 +573,12 @@ export class WebBluetoothAdapter extends BaseAdapter {
|
|
|
335
573
|
return existingInfo.device;
|
|
336
574
|
}
|
|
337
575
|
|
|
576
|
+
// Try to find device from discovered devices
|
|
577
|
+
const discoveredDevice = this.discoveredDevices.get(deviceId);
|
|
578
|
+
if (discoveredDevice?.device) {
|
|
579
|
+
return discoveredDevice.device;
|
|
580
|
+
}
|
|
581
|
+
|
|
338
582
|
// Request a new device
|
|
339
583
|
return this.requestDevice();
|
|
340
584
|
}
|
|
@@ -414,6 +658,13 @@ export class WebBluetoothAdapter extends BaseAdapter {
|
|
|
414
658
|
*/
|
|
415
659
|
private handleDisconnection(deviceId: string): void {
|
|
416
660
|
this.logger.warn('Device disconnected unexpectedly:', deviceId);
|
|
661
|
+
|
|
662
|
+
// Clear any pending cleanup
|
|
663
|
+
if (this.connectionCleanupTimeout) {
|
|
664
|
+
clearTimeout(this.connectionCleanupTimeout);
|
|
665
|
+
this.connectionCleanupTimeout = null;
|
|
666
|
+
}
|
|
667
|
+
|
|
417
668
|
this.cleanupDeviceInfo(deviceId);
|
|
418
669
|
this.updateState(PrinterState.DISCONNECTED);
|
|
419
670
|
}
|
|
@@ -425,4 +676,14 @@ export class WebBluetoothAdapter extends BaseAdapter {
|
|
|
425
676
|
this.devices.delete(deviceId);
|
|
426
677
|
this.cleanupDevice(deviceId);
|
|
427
678
|
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Generate a fallback device ID when device.id is not available
|
|
682
|
+
* Uses device name + first seen timestamp as identifier
|
|
683
|
+
*/
|
|
684
|
+
private generateFallbackDeviceId(device: BluetoothDevice): string {
|
|
685
|
+
const name = device.name || 'unknown';
|
|
686
|
+
const timestamp = Date.now().toString(36);
|
|
687
|
+
return `fallback_${name}_${timestamp}`;
|
|
688
|
+
}
|
|
428
689
|
}
|
package/src/adapters/index.ts
CHANGED
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export { BaseAdapter, MiniProgramAdapter } from './BaseAdapter';
|
|
6
|
-
export type {
|
|
6
|
+
export type {
|
|
7
|
+
MiniProgramBLEApi,
|
|
8
|
+
ServiceInfo,
|
|
9
|
+
BLECharacteristic,
|
|
10
|
+
BLECharacteristicProperties,
|
|
11
|
+
} from './BaseAdapter';
|
|
7
12
|
export { TaroAdapter } from './TaroAdapter';
|
|
8
13
|
export { AlipayAdapter } from './AlipayAdapter';
|
|
9
14
|
export { BaiduAdapter } from './BaiduAdapter';
|