taro-bluetooth-print 2.4.1 → 2.5.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/CHANGELOG.md +23 -0
- package/README.md +10 -2
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/types/adapters/QQAdapter.d.ts +22 -0
- package/dist/types/adapters/ReactNativeAdapter.d.ts +111 -0
- package/dist/types/adapters/index.d.ts +13 -0
- package/dist/types/drivers/StarPrinter.d.ts +243 -0
- package/dist/types/drivers/index.d.ts +1 -0
- package/dist/types/encoding/EncodingService.d.ts +41 -2
- package/dist/types/encoding/index.d.ts +2 -1
- package/dist/types/encoding/korean-japanese.d.ts +127 -0
- package/dist/types/services/BatchPrintManager.d.ts +98 -5
- package/dist/types/services/PrintStatistics.d.ts +189 -0
- package/dist/types/services/ScheduledRetryManager.d.ts +213 -0
- package/dist/types/services/index.d.ts +2 -0
- package/dist/types/utils/image.d.ts +40 -119
- package/dist/types/utils/platform.d.ts +2 -0
- package/package.json +1 -1
- package/src/adapters/AdapterFactory.ts +5 -0
- package/src/adapters/QQAdapter.ts +36 -0
- package/src/adapters/ReactNativeAdapter.ts +517 -0
- package/src/adapters/index.ts +14 -0
- package/src/drivers/StarPrinter.ts +555 -0
- package/src/drivers/index.ts +10 -0
- package/src/encoding/EncodingService.ts +261 -4
- package/src/encoding/index.ts +17 -1
- package/src/encoding/korean-japanese.ts +289 -0
- package/src/services/BatchPrintManager.ts +292 -16
- package/src/services/PrintStatistics.ts +504 -0
- package/src/services/ScheduledRetryManager.ts +564 -0
- package/src/services/index.ts +16 -0
- package/src/utils/image.ts +476 -342
- package/src/utils/platform.ts +20 -34
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native Bluetooth Adapter
|
|
3
|
+
* Implements the IPrinterAdapter interface for React Native using react-native-ble-plx
|
|
4
|
+
*
|
|
5
|
+
* React Native does not have a native Web BLE API, so this adapter uses
|
|
6
|
+
* the react-native-ble-plx library for BLE operations on iOS and Android.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Platform type - React Native availability is checked at runtime via Platform.OS
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
const Platform = (globalThis as any).Platform;
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
BaseAdapter,
|
|
15
|
+
} from './BaseAdapter';
|
|
16
|
+
import { IPrinterAdapter, IAdapterOptions, PrinterState } from '@/types';
|
|
17
|
+
import { Logger } from '@/utils/logger';
|
|
18
|
+
import { BluetoothPrintError, ErrorCode } from '@/errors/BluetoothError';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* BLE characteristic info with full UUIDs
|
|
22
|
+
*/
|
|
23
|
+
interface RNCharacteristic {
|
|
24
|
+
uuid: string;
|
|
25
|
+
isWritableWithResponse: boolean;
|
|
26
|
+
isWritableWithoutResponse: boolean;
|
|
27
|
+
isReadable: boolean;
|
|
28
|
+
isNotifiable: boolean;
|
|
29
|
+
isIndicatable: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* RNService info with characteristic list
|
|
34
|
+
*/
|
|
35
|
+
interface RNService {
|
|
36
|
+
uuid: string;
|
|
37
|
+
characteristics: RNCharacteristic[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* React Native BLE Manager interface
|
|
42
|
+
* Compatible with react-native-ble-plx API
|
|
43
|
+
*/
|
|
44
|
+
interface BLEManager {
|
|
45
|
+
startDeviceScan(
|
|
46
|
+
serviceUUIDs: string[] | null,
|
|
47
|
+
options: Record<string, unknown> | null,
|
|
48
|
+
onDeviceScanned: (error: unknown, device: unknown) => void
|
|
49
|
+
): void;
|
|
50
|
+
stopDeviceScan(): void;
|
|
51
|
+
connectToDevice(
|
|
52
|
+
deviceIdentifier: string,
|
|
53
|
+
options: Record<string, unknown>
|
|
54
|
+
): Promise<unknown>;
|
|
55
|
+
disconnectFromDevice(
|
|
56
|
+
deviceIdentifier: string,
|
|
57
|
+
force?: boolean
|
|
58
|
+
): Promise<unknown>;
|
|
59
|
+
discoverAllServicesAndCharacteristicsForDevice(
|
|
60
|
+
deviceIdentifier: string
|
|
61
|
+
): Promise<unknown>;
|
|
62
|
+
writeCharacteristicWithResponseForDevice(
|
|
63
|
+
deviceIdentifier: string,
|
|
64
|
+
serviceUUID: string,
|
|
65
|
+
characteristicUUID: string,
|
|
66
|
+
value: string,
|
|
67
|
+
transactionId?: string
|
|
68
|
+
): Promise<unknown>;
|
|
69
|
+
writeCharacteristicWithoutResponseForDevice(
|
|
70
|
+
deviceIdentifier: string,
|
|
71
|
+
serviceUUID: string,
|
|
72
|
+
characteristicUUID: string,
|
|
73
|
+
value: string,
|
|
74
|
+
transactionId?: string
|
|
75
|
+
): Promise<unknown>;
|
|
76
|
+
readCharacteristicForDevice(
|
|
77
|
+
deviceIdentifier: string,
|
|
78
|
+
serviceUUID: string,
|
|
79
|
+
characteristicUUID: string,
|
|
80
|
+
transactionId?: string
|
|
81
|
+
): Promise<unknown>;
|
|
82
|
+
monitorCharacteristicForDevice(
|
|
83
|
+
deviceIdentifier: string,
|
|
84
|
+
serviceUUID: string,
|
|
85
|
+
characteristicUUID: string,
|
|
86
|
+
onUpdate: (error: unknown, characteristic: unknown) => void,
|
|
87
|
+
transactionId?: string
|
|
88
|
+
): { remove: () => void };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* BLE Device interface from react-native-ble-plx
|
|
93
|
+
*/
|
|
94
|
+
interface RNDevice {
|
|
95
|
+
id: string;
|
|
96
|
+
name: string | null;
|
|
97
|
+
isConnected: boolean;
|
|
98
|
+
rssi: number;
|
|
99
|
+
mtu: number;
|
|
100
|
+
requestConnectionPriority(): Promise<void>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* React Native Bluetooth Low Energy adapter
|
|
105
|
+
*
|
|
106
|
+
* Uses react-native-ble-plx for BLE operations on iOS and Android.
|
|
107
|
+
* This adapter does NOT extend MiniProgramAdapter because React Native
|
|
108
|
+
* has a fundamentally different BLE API compared to mini-program platforms.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* import BleManager from 'react-native-ble-plx';
|
|
113
|
+
* import { ReactNativeAdapter } from 'taro-bluetooth-print';
|
|
114
|
+
*
|
|
115
|
+
* BleManager.start({ showAlert: false });
|
|
116
|
+
*
|
|
117
|
+
* const adapter = new ReactNativeAdapter({ bleManager: BleManager });
|
|
118
|
+
* await adapter.connect('device-uuid-123');
|
|
119
|
+
* await adapter.write('device-uuid-123', buffer);
|
|
120
|
+
* await adapter.disconnect('device-uuid-123');
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export class ReactNativeAdapter extends BaseAdapter implements IPrinterAdapter {
|
|
124
|
+
private bleManager: BLEManager;
|
|
125
|
+
private deviceCache: Map<string, RNDevice> = new Map();
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Creates a new ReactNativeAdapter instance
|
|
129
|
+
*
|
|
130
|
+
* @param options - Configuration options
|
|
131
|
+
* @param options.bleManager - BLE Manager instance (e.g., from react-native-ble-plx)
|
|
132
|
+
* @throws {Error} If bleManager is not provided or not supported
|
|
133
|
+
*/
|
|
134
|
+
constructor(options: { bleManager: BLEManager }) {
|
|
135
|
+
super();
|
|
136
|
+
|
|
137
|
+
if (!options?.bleManager) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
'ReactNativeAdapter requires a bleManager instance (e.g., react-native-ble-plx). ' +
|
|
140
|
+
'Please pass { bleManager: yourBleManager } in the constructor.'
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.bleManager = options.bleManager;
|
|
145
|
+
|
|
146
|
+
// Validate platform
|
|
147
|
+
if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
|
|
148
|
+
Logger.scope('ReactNativeAdapter').warn(
|
|
149
|
+
`Running on unsupported platform: ${Platform.OS}. BLE may not work correctly.`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Connect to a Bluetooth device and discover services
|
|
156
|
+
*
|
|
157
|
+
* @param deviceId - Unique identifier (UUID) of the device to connect to
|
|
158
|
+
* @throws {BluetoothPrintError} When connection fails or device not found
|
|
159
|
+
*/
|
|
160
|
+
async connect(deviceId: string): Promise<void> {
|
|
161
|
+
this.validateDeviceId(deviceId);
|
|
162
|
+
|
|
163
|
+
if (this.isDeviceConnected(deviceId)) {
|
|
164
|
+
Logger.scope('ReactNativeAdapter').warn('Device already connected:', deviceId);
|
|
165
|
+
this.updateState(PrinterState.CONNECTED);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.updateState(PrinterState.CONNECTING);
|
|
170
|
+
Logger.scope('ReactNativeAdapter').debug('Connecting to device:', deviceId);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
// Add connection timeout
|
|
174
|
+
const timeoutMs = 15000;
|
|
175
|
+
const connectionPromise = this.performConnect(deviceId);
|
|
176
|
+
|
|
177
|
+
let timeoutHandle: NodeJS.Timeout | null = null;
|
|
178
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
179
|
+
timeoutHandle = setTimeout(() => {
|
|
180
|
+
reject(new Error('Connection timeout'));
|
|
181
|
+
}, timeoutMs);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const device = await Promise.race([connectionPromise, timeoutPromise]);
|
|
185
|
+
|
|
186
|
+
if (timeoutHandle) {
|
|
187
|
+
clearTimeout(timeoutHandle);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
this.deviceCache.set(deviceId, device as RNDevice);
|
|
191
|
+
await this.discoverServices(deviceId, device as RNDevice);
|
|
192
|
+
|
|
193
|
+
this.updateState(PrinterState.CONNECTED);
|
|
194
|
+
Logger.scope('ReactNativeAdapter').info('Device connected successfully');
|
|
195
|
+
} catch (error) {
|
|
196
|
+
this.updateState(PrinterState.DISCONNECTED);
|
|
197
|
+
this.cleanupDevice(deviceId);
|
|
198
|
+
|
|
199
|
+
const errorMsg = (error as Error).message || '';
|
|
200
|
+
if (errorMsg.includes('timeout')) {
|
|
201
|
+
throw new BluetoothPrintError(
|
|
202
|
+
ErrorCode.CONNECTION_TIMEOUT,
|
|
203
|
+
`Connection to device ${deviceId} timed out`,
|
|
204
|
+
error as Error
|
|
205
|
+
);
|
|
206
|
+
} else if (errorMsg.includes('not found') || errorMsg.includes('not exist')) {
|
|
207
|
+
throw new BluetoothPrintError(
|
|
208
|
+
ErrorCode.DEVICE_NOT_FOUND,
|
|
209
|
+
`Device ${deviceId} not found`,
|
|
210
|
+
error as Error
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
throw new BluetoothPrintError(
|
|
215
|
+
ErrorCode.CONNECTION_FAILED,
|
|
216
|
+
`Failed to connect to device ${deviceId}`,
|
|
217
|
+
error as Error
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Perform the actual BLE connection
|
|
224
|
+
*/
|
|
225
|
+
private async performConnect(deviceId: string): Promise<unknown> {
|
|
226
|
+
const device = await (this.bleManager.connectToDevice(deviceId, {
|
|
227
|
+
timeout: 10000,
|
|
228
|
+
}) as Promise<RNDevice>);
|
|
229
|
+
|
|
230
|
+
// Request connection priority for better throughput
|
|
231
|
+
try {
|
|
232
|
+
await device.requestConnectionPriority();
|
|
233
|
+
} catch {
|
|
234
|
+
// Ignore priority request errors
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return device;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Disconnect from a Bluetooth device
|
|
242
|
+
*
|
|
243
|
+
* @param deviceId - Unique identifier of the device to disconnect from
|
|
244
|
+
*/
|
|
245
|
+
async disconnect(deviceId: string): Promise<void> {
|
|
246
|
+
this.validateDeviceId(deviceId);
|
|
247
|
+
this.updateState(PrinterState.DISCONNECTING);
|
|
248
|
+
Logger.scope('ReactNativeAdapter').debug('Disconnecting from device:', deviceId);
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
await (this.bleManager.disconnectFromDevice(deviceId, true) as Promise<void>);
|
|
252
|
+
this.cleanupDevice(deviceId);
|
|
253
|
+
this.deviceCache.delete(deviceId);
|
|
254
|
+
this.updateState(PrinterState.DISCONNECTED);
|
|
255
|
+
Logger.scope('ReactNativeAdapter').info('Device disconnected successfully');
|
|
256
|
+
} catch (error) {
|
|
257
|
+
Logger.scope('ReactNativeAdapter').warn('Disconnect error (ignored):', error);
|
|
258
|
+
this.cleanupDevice(deviceId);
|
|
259
|
+
this.deviceCache.delete(deviceId);
|
|
260
|
+
this.updateState(PrinterState.DISCONNECTED);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Write data to the Bluetooth device in chunks
|
|
266
|
+
*
|
|
267
|
+
* Features:
|
|
268
|
+
* - Automatic chunk size adjustment
|
|
269
|
+
* - Dynamic delay for congestion control
|
|
270
|
+
* - Retry with exponential backoff
|
|
271
|
+
* - Write timeout per chunk
|
|
272
|
+
*
|
|
273
|
+
* @param deviceId - Unique identifier of the connected device
|
|
274
|
+
* @param buffer - Data to write as ArrayBuffer
|
|
275
|
+
* @param options - Optional write settings (chunkSize, delay, retries)
|
|
276
|
+
* @throws {BluetoothPrintError} When write fails after all retries
|
|
277
|
+
*/
|
|
278
|
+
async write(deviceId: string, buffer: ArrayBuffer, options?: IAdapterOptions): Promise<void> {
|
|
279
|
+
this.validateDeviceId(deviceId);
|
|
280
|
+
this.validateBuffer(buffer);
|
|
281
|
+
const serviceInfo = this.getServiceInfo(deviceId);
|
|
282
|
+
const validatedOptions = this.validateOptions(options);
|
|
283
|
+
|
|
284
|
+
const device = this.deviceCache.get(deviceId);
|
|
285
|
+
if (!device || !device.isConnected) {
|
|
286
|
+
this.cleanupDevice(deviceId);
|
|
287
|
+
throw new BluetoothPrintError(
|
|
288
|
+
ErrorCode.DEVICE_DISCONNECTED,
|
|
289
|
+
`Device ${deviceId} is not connected`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let { chunkSize } = validatedOptions;
|
|
294
|
+
const { delay, retries } = validatedOptions;
|
|
295
|
+
const data = new Uint8Array(buffer);
|
|
296
|
+
const totalChunks = Math.ceil(data.length / chunkSize);
|
|
297
|
+
|
|
298
|
+
Logger.scope('ReactNativeAdapter').debug(
|
|
299
|
+
`Writing ${data.length} bytes in ${totalChunks} chunks`
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
if (data.length === 0) {
|
|
303
|
+
Logger.scope('ReactNativeAdapter').warn('No data to write');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Adaptive transmission parameters
|
|
308
|
+
let successCount = 0;
|
|
309
|
+
let consecutiveFailures = 0;
|
|
310
|
+
const minChunkSize = 10;
|
|
311
|
+
const maxChunkSize = Math.min(512, device.mtu - 5); // Respect MTU if available
|
|
312
|
+
let baseDelay = delay;
|
|
313
|
+
const maxDelay = 200;
|
|
314
|
+
|
|
315
|
+
for (let i = 0; i < data.length; i += chunkSize) {
|
|
316
|
+
const chunk = data.slice(i, i + chunkSize);
|
|
317
|
+
const chunkNum = Math.floor(i / chunkSize) + 1;
|
|
318
|
+
let attempt = 0;
|
|
319
|
+
let writeSuccess = false;
|
|
320
|
+
|
|
321
|
+
while (attempt <= retries) {
|
|
322
|
+
try {
|
|
323
|
+
// Convert Uint8Array to base64 string for react-native-ble-plx
|
|
324
|
+
const base64Value = this.arrayBufferToBase64(chunk.buffer);
|
|
325
|
+
|
|
326
|
+
// Timeout per write
|
|
327
|
+
const timeoutMs = Math.max(2000, Math.min(10000, 1000 + chunk.length * 10));
|
|
328
|
+
let timeoutHandle: NodeJS.Timeout | null = null;
|
|
329
|
+
|
|
330
|
+
const writePromise = (async () => {
|
|
331
|
+
try {
|
|
332
|
+
await (this.bleManager.writeCharacteristicWithResponseForDevice(
|
|
333
|
+
deviceId,
|
|
334
|
+
serviceInfo.serviceId,
|
|
335
|
+
serviceInfo.characteristicId,
|
|
336
|
+
base64Value
|
|
337
|
+
) as Promise<void>);
|
|
338
|
+
} catch {
|
|
339
|
+
// Fallback to without-response if with-response fails
|
|
340
|
+
await (this.bleManager.writeCharacteristicWithoutResponseForDevice(
|
|
341
|
+
deviceId,
|
|
342
|
+
serviceInfo.serviceId,
|
|
343
|
+
serviceInfo.characteristicId,
|
|
344
|
+
base64Value
|
|
345
|
+
) as Promise<void>);
|
|
346
|
+
}
|
|
347
|
+
})();
|
|
348
|
+
|
|
349
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
350
|
+
timeoutHandle = setTimeout(() => {
|
|
351
|
+
reject(new Error(`Write timeout after ${timeoutMs}ms`));
|
|
352
|
+
}, timeoutMs);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
await Promise.race([writePromise, timeoutPromise]);
|
|
356
|
+
|
|
357
|
+
if (timeoutHandle) {
|
|
358
|
+
clearTimeout(timeoutHandle);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
Logger.scope('ReactNativeAdapter').debug(
|
|
362
|
+
`Chunk ${chunkNum}/${totalChunks} written successfully`
|
|
363
|
+
);
|
|
364
|
+
writeSuccess = true;
|
|
365
|
+
break;
|
|
366
|
+
} catch (error) {
|
|
367
|
+
attempt++;
|
|
368
|
+
if (attempt > retries) {
|
|
369
|
+
Logger.scope('ReactNativeAdapter').error(
|
|
370
|
+
`Chunk ${chunkNum} failed after ${retries} retries`
|
|
371
|
+
);
|
|
372
|
+
throw new BluetoothPrintError(
|
|
373
|
+
ErrorCode.WRITE_FAILED,
|
|
374
|
+
`Failed to write chunk ${chunkNum}/${totalChunks}`,
|
|
375
|
+
error as Error
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
Logger.scope('ReactNativeAdapter').warn(
|
|
379
|
+
`Chunk ${chunkNum} write failed, retry ${attempt}/${retries}`
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
const retryDelay = baseDelay * Math.pow(2, attempt - 1);
|
|
383
|
+
await new Promise(r => setTimeout(r, Math.min(retryDelay, maxDelay)));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Adaptive chunk size and delay adjustment
|
|
388
|
+
if (writeSuccess) {
|
|
389
|
+
successCount++;
|
|
390
|
+
consecutiveFailures = 0;
|
|
391
|
+
|
|
392
|
+
if (successCount % 3 === 0 && chunkSize < maxChunkSize) {
|
|
393
|
+
chunkSize = Math.min(maxChunkSize, chunkSize + 5);
|
|
394
|
+
baseDelay = Math.max(baseDelay / 1.2, validatedOptions.delay);
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
consecutiveFailures++;
|
|
398
|
+
successCount = Math.max(0, successCount - 1);
|
|
399
|
+
|
|
400
|
+
if (consecutiveFailures >= 2 && chunkSize > minChunkSize) {
|
|
401
|
+
chunkSize = Math.max(minChunkSize, chunkSize - 5);
|
|
402
|
+
baseDelay = Math.min(baseDelay * 1.5, maxDelay);
|
|
403
|
+
consecutiveFailures = 0;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Inter-chunk delay
|
|
408
|
+
if (i + chunkSize < data.length) {
|
|
409
|
+
await new Promise(r => setTimeout(r, baseDelay));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
Logger.scope('ReactNativeAdapter').info(`Successfully wrote ${data.length} bytes`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Start discovering nearby Bluetooth devices
|
|
418
|
+
*
|
|
419
|
+
* Note: This is optional in IPrinterAdapter. In React Native BLE,
|
|
420
|
+
* device discovery is typically done via scan events.
|
|
421
|
+
*/
|
|
422
|
+
startDiscovery?(): Promise<void> {
|
|
423
|
+
return new Promise((resolve, reject) => {
|
|
424
|
+
try {
|
|
425
|
+
this.bleManager.startDeviceScan(
|
|
426
|
+
null,
|
|
427
|
+
{ allowDuplicates: false },
|
|
428
|
+
(error: unknown) => {
|
|
429
|
+
if (error) {
|
|
430
|
+
reject(error);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
resolve();
|
|
435
|
+
} catch (error) {
|
|
436
|
+
reject(error);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Stop discovering nearby Bluetooth devices
|
|
443
|
+
*/
|
|
444
|
+
stopDiscovery?(): Promise<void> {
|
|
445
|
+
return new Promise((resolve) => {
|
|
446
|
+
this.bleManager.stopDeviceScan();
|
|
447
|
+
resolve();
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Discover services and characteristics for a connected device
|
|
453
|
+
*
|
|
454
|
+
* @param deviceId - Device identifier
|
|
455
|
+
* @param device - Connected device object
|
|
456
|
+
*/
|
|
457
|
+
private async discoverServices(deviceId: string, _device: RNDevice): Promise<void> {
|
|
458
|
+
Logger.scope('ReactNativeAdapter').debug('Discovering services for device:', deviceId);
|
|
459
|
+
|
|
460
|
+
try {
|
|
461
|
+
const servicesResult = await (this.bleManager.discoverAllServicesAndCharacteristicsForDevice(
|
|
462
|
+
deviceId
|
|
463
|
+
) as Promise<{ services: RNService[] }>);
|
|
464
|
+
|
|
465
|
+
const services = servicesResult.services || [];
|
|
466
|
+
|
|
467
|
+
for (const service of services) {
|
|
468
|
+
const writeChar = service.characteristics.find(
|
|
469
|
+
(c: RNCharacteristic) =>
|
|
470
|
+
c.isWritableWithResponse || c.isWritableWithoutResponse
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
if (writeChar) {
|
|
474
|
+
this.serviceCache.set(deviceId, {
|
|
475
|
+
serviceId: service.uuid,
|
|
476
|
+
characteristicId: writeChar.uuid,
|
|
477
|
+
});
|
|
478
|
+
Logger.scope('ReactNativeAdapter').info('Found writeable characteristic:', {
|
|
479
|
+
service: service.uuid,
|
|
480
|
+
characteristic: writeChar.uuid,
|
|
481
|
+
});
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
throw new BluetoothPrintError(
|
|
487
|
+
ErrorCode.CHARACTERISTIC_NOT_FOUND,
|
|
488
|
+
'No writeable characteristic found. Make sure the device is a supported printer.'
|
|
489
|
+
);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
if (error instanceof BluetoothPrintError) {
|
|
492
|
+
throw error;
|
|
493
|
+
}
|
|
494
|
+
throw new BluetoothPrintError(
|
|
495
|
+
ErrorCode.SERVICE_DISCOVERY_FAILED,
|
|
496
|
+
'Failed to discover device services',
|
|
497
|
+
error as Error
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Convert ArrayBuffer to base64 string for react-native-ble-plx
|
|
504
|
+
*
|
|
505
|
+
* @param buffer - ArrayBuffer to convert
|
|
506
|
+
* @returns Base64 encoded string
|
|
507
|
+
*/
|
|
508
|
+
private arrayBufferToBase64(buffer: ArrayBuffer): string {
|
|
509
|
+
const bytes = new Uint8Array(buffer);
|
|
510
|
+
let binary = '';
|
|
511
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
512
|
+
binary += String.fromCharCode(bytes[i] ?? 0);
|
|
513
|
+
}
|
|
514
|
+
// Use built-in btoa (available in React Native)
|
|
515
|
+
return globalThis.btoa(binary);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapters barrel export
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { BaseAdapter, MiniProgramAdapter } from './BaseAdapter';
|
|
6
|
+
export type { MiniProgramBLEApi, ServiceInfo, BLECharacteristic, BLECharacteristicProperties } from './BaseAdapter';
|
|
7
|
+
export { TaroAdapter } from './TaroAdapter';
|
|
8
|
+
export { AlipayAdapter } from './AlipayAdapter';
|
|
9
|
+
export { BaiduAdapter } from './BaiduAdapter';
|
|
10
|
+
export { ByteDanceAdapter } from './ByteDanceAdapter';
|
|
11
|
+
export { QQAdapter } from './QQAdapter';
|
|
12
|
+
export { ReactNativeAdapter } from './ReactNativeAdapter';
|
|
13
|
+
export { WebBluetoothAdapter } from './WebBluetoothAdapter';
|
|
14
|
+
export { AdapterFactory } from './AdapterFactory';
|