bitchat-node 0.1.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 (102) hide show
  1. package/README.md +223 -0
  2. package/dist/bin/bitchat.d.ts +7 -0
  3. package/dist/bin/bitchat.d.ts.map +1 -0
  4. package/dist/bin/bitchat.js +69 -0
  5. package/dist/bin/bitchat.js.map +1 -0
  6. package/dist/client.d.ts +77 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +411 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/crypto/index.d.ts +6 -0
  11. package/dist/crypto/index.d.ts.map +1 -0
  12. package/dist/crypto/index.js +6 -0
  13. package/dist/crypto/index.js.map +1 -0
  14. package/dist/crypto/noise.d.ts +72 -0
  15. package/dist/crypto/noise.d.ts.map +1 -0
  16. package/dist/crypto/noise.js +470 -0
  17. package/dist/crypto/noise.js.map +1 -0
  18. package/dist/crypto/signing.d.ts +34 -0
  19. package/dist/crypto/signing.d.ts.map +1 -0
  20. package/dist/crypto/signing.js +56 -0
  21. package/dist/crypto/signing.js.map +1 -0
  22. package/dist/index.d.ts +32 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +48 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/mesh/deduplicator.d.ts +48 -0
  27. package/dist/mesh/deduplicator.d.ts.map +1 -0
  28. package/dist/mesh/deduplicator.js +107 -0
  29. package/dist/mesh/deduplicator.js.map +1 -0
  30. package/dist/mesh/index.d.ts +6 -0
  31. package/dist/mesh/index.d.ts.map +1 -0
  32. package/dist/mesh/index.js +6 -0
  33. package/dist/mesh/index.js.map +1 -0
  34. package/dist/mesh/router.d.ts +90 -0
  35. package/dist/mesh/router.d.ts.map +1 -0
  36. package/dist/mesh/router.js +204 -0
  37. package/dist/mesh/router.js.map +1 -0
  38. package/dist/protocol/binary.d.ts +37 -0
  39. package/dist/protocol/binary.d.ts.map +1 -0
  40. package/dist/protocol/binary.js +310 -0
  41. package/dist/protocol/binary.js.map +1 -0
  42. package/dist/protocol/constants.d.ts +30 -0
  43. package/dist/protocol/constants.d.ts.map +1 -0
  44. package/dist/protocol/constants.js +37 -0
  45. package/dist/protocol/constants.js.map +1 -0
  46. package/dist/protocol/index.d.ts +8 -0
  47. package/dist/protocol/index.d.ts.map +1 -0
  48. package/dist/protocol/index.js +8 -0
  49. package/dist/protocol/index.js.map +1 -0
  50. package/dist/protocol/packets.d.ts +38 -0
  51. package/dist/protocol/packets.d.ts.map +1 -0
  52. package/dist/protocol/packets.js +177 -0
  53. package/dist/protocol/packets.js.map +1 -0
  54. package/dist/protocol/types.d.ts +134 -0
  55. package/dist/protocol/types.d.ts.map +1 -0
  56. package/dist/protocol/types.js +108 -0
  57. package/dist/protocol/types.js.map +1 -0
  58. package/dist/session/index.d.ts +5 -0
  59. package/dist/session/index.d.ts.map +1 -0
  60. package/dist/session/index.js +5 -0
  61. package/dist/session/index.js.map +1 -0
  62. package/dist/session/manager.d.ts +113 -0
  63. package/dist/session/manager.d.ts.map +1 -0
  64. package/dist/session/manager.js +371 -0
  65. package/dist/session/manager.js.map +1 -0
  66. package/dist/transport/ble.d.ts +92 -0
  67. package/dist/transport/ble.d.ts.map +1 -0
  68. package/dist/transport/ble.js +434 -0
  69. package/dist/transport/ble.js.map +1 -0
  70. package/dist/transport/index.d.ts +5 -0
  71. package/dist/transport/index.d.ts.map +1 -0
  72. package/dist/transport/index.js +5 -0
  73. package/dist/transport/index.js.map +1 -0
  74. package/dist/ui/index.d.ts +2 -0
  75. package/dist/ui/index.d.ts.map +1 -0
  76. package/dist/ui/index.js +2 -0
  77. package/dist/ui/index.js.map +1 -0
  78. package/dist/ui/server.d.ts +16 -0
  79. package/dist/ui/server.d.ts.map +1 -0
  80. package/dist/ui/server.js +510 -0
  81. package/dist/ui/server.js.map +1 -0
  82. package/package.json +79 -0
  83. package/src/bin/bitchat.ts +87 -0
  84. package/src/client.ts +519 -0
  85. package/src/crypto/index.ts +22 -0
  86. package/src/crypto/noise.ts +574 -0
  87. package/src/crypto/signing.ts +66 -0
  88. package/src/index.ts +95 -0
  89. package/src/mesh/deduplicator.ts +129 -0
  90. package/src/mesh/index.ts +6 -0
  91. package/src/mesh/router.ts +258 -0
  92. package/src/protocol/binary.ts +345 -0
  93. package/src/protocol/constants.ts +43 -0
  94. package/src/protocol/index.ts +15 -0
  95. package/src/protocol/packets.ts +223 -0
  96. package/src/protocol/types.ts +182 -0
  97. package/src/session/index.ts +9 -0
  98. package/src/session/manager.ts +476 -0
  99. package/src/transport/ble.ts +553 -0
  100. package/src/transport/index.ts +10 -0
  101. package/src/ui/index.ts +1 -0
  102. package/src/ui/server.ts +569 -0
@@ -0,0 +1,553 @@
1
+ /**
2
+ * BLE Transport
3
+ * Handles Bluetooth Low Energy communication for Bitchat mesh
4
+ *
5
+ * Uses noble for central mode (scanning/connecting) and
6
+ * bleno for peripheral mode (advertising/accepting)
7
+ */
8
+
9
+ import { EventEmitter } from 'node:events';
10
+ import type { Link } from '../mesh/router.js';
11
+ import {
12
+ BLE_MAX_MTU,
13
+ CHARACTERISTIC_UUID,
14
+ SERVICE_UUID,
15
+ SERVICE_UUID_TESTNET,
16
+ } from '../protocol/constants.js';
17
+ import type { PeerID } from '../protocol/types.js';
18
+
19
+ // Types for noble
20
+ type NobleModule = typeof import('@abandonware/noble');
21
+ type Peripheral = import('@abandonware/noble').Peripheral;
22
+ type Characteristic = import('@abandonware/noble').Characteristic;
23
+
24
+ // Types for bleno
25
+ type BlenoModule = typeof import('@abandonware/bleno');
26
+
27
+ export interface BLETransportConfig {
28
+ serviceUUID: string;
29
+ characteristicUUID: string;
30
+ testnet: boolean;
31
+ enableCentral: boolean;
32
+ enablePeripheral: boolean;
33
+ scanDuplicates: boolean;
34
+ advertisingName?: string;
35
+ }
36
+
37
+ const DEFAULT_CONFIG: BLETransportConfig = {
38
+ serviceUUID: SERVICE_UUID,
39
+ characteristicUUID: CHARACTERISTIC_UUID,
40
+ testnet: false,
41
+ enableCentral: true,
42
+ enablePeripheral: true,
43
+ scanDuplicates: true, // Enable to allow reconnection attempts
44
+ };
45
+
46
+ interface ConnectedPeripheral {
47
+ peripheral: Peripheral;
48
+ characteristic?: Characteristic;
49
+ peerID?: PeerID;
50
+ mtu: number;
51
+ }
52
+
53
+ export interface BLETransportEvents {
54
+ ready: () => void;
55
+ data: (data: Uint8Array, fromLink: BLELink) => void;
56
+ 'link:connected': (link: BLELink) => void;
57
+ 'link:disconnected': (linkId: string) => void;
58
+ error: (error: Error, context: string) => void;
59
+ state: (state: string) => void;
60
+ }
61
+
62
+ /**
63
+ * BLE link implementation
64
+ */
65
+ export class BLELink implements Link {
66
+ readonly id: string;
67
+ private peripheral: Peripheral;
68
+ private characteristic: Characteristic;
69
+ private _peerID?: PeerID;
70
+ private mtu: number;
71
+
72
+ constructor(peripheral: Peripheral, characteristic: Characteristic, mtu: number = BLE_MAX_MTU) {
73
+ this.id = peripheral.uuid;
74
+ this.peripheral = peripheral;
75
+ this.characteristic = characteristic;
76
+ this.mtu = mtu;
77
+ }
78
+
79
+ get peerID(): PeerID | undefined {
80
+ return this._peerID;
81
+ }
82
+
83
+ setPeerID(peerID: PeerID): void {
84
+ this._peerID = peerID;
85
+ }
86
+
87
+ async send(data: Uint8Array): Promise<void> {
88
+ // Fragment if necessary
89
+ if (data.length > this.mtu) {
90
+ // TODO: Implement fragmentation
91
+ throw new Error('Message too large for MTU, fragmentation not yet implemented');
92
+ }
93
+
94
+ console.log('[BLE] Sending', data.length, 'bytes to', this.id);
95
+ console.log('[BLE] First 50 bytes:', Buffer.from(data.slice(0, 50)).toString('hex'));
96
+ console.log('[BLE] Last 20 bytes:', Buffer.from(data.slice(-20)).toString('hex'));
97
+
98
+ const buf = Buffer.from(data);
99
+ console.log('[BLE] Buffer check - is Buffer:', Buffer.isBuffer(buf), 'length:', buf.length);
100
+
101
+ // Try write WITHOUT response (withoutResponse = true)
102
+ // This uses a different BLE write type that might work better
103
+ return new Promise((resolve, reject) => {
104
+ this.characteristic.write(buf, true, (error) => {
105
+ if (error) {
106
+ console.error('[BLE] Send error:', error);
107
+ reject(error);
108
+ } else {
109
+ console.log('[BLE] Send success (without response)');
110
+ resolve();
111
+ }
112
+ });
113
+ });
114
+ }
115
+
116
+ get isConnected(): boolean {
117
+ return this.peripheral.state === 'connected';
118
+ }
119
+ }
120
+
121
+ /**
122
+ * BLE Transport - handles Bluetooth mesh networking
123
+ */
124
+ export class BLETransport extends EventEmitter {
125
+ private config: BLETransportConfig;
126
+ private noble?: NobleModule;
127
+ private bleno?: BlenoModule;
128
+ private running = false;
129
+
130
+ // Connected peripherals (central mode)
131
+ private peripherals = new Map<string, ConnectedPeripheral>();
132
+ private links = new Map<string, BLELink>();
133
+
134
+ // Subscribed centrals (peripheral mode)
135
+ private subscribedCentrals = new Set<string>();
136
+ private notifyCallback?: (data: Buffer) => void;
137
+
138
+ constructor(config: Partial<BLETransportConfig> = {}) {
139
+ super();
140
+ const baseConfig = { ...DEFAULT_CONFIG, ...config };
141
+ // Use testnet UUID if testnet is true and no explicit serviceUUID was provided
142
+ if (baseConfig.testnet && !config.serviceUUID) {
143
+ baseConfig.serviceUUID = SERVICE_UUID_TESTNET;
144
+ }
145
+ this.config = baseConfig;
146
+ console.log(
147
+ '[BLE] Config: testnet =',
148
+ this.config.testnet,
149
+ ', serviceUUID =',
150
+ this.config.serviceUUID
151
+ );
152
+ }
153
+
154
+ /**
155
+ * Start the BLE transport
156
+ */
157
+ async start(): Promise<void> {
158
+ if (this.running) return;
159
+
160
+ try {
161
+ // Initialize central mode (noble)
162
+ if (this.config.enableCentral) {
163
+ await this.initCentral();
164
+ }
165
+
166
+ // Initialize peripheral mode (bleno)
167
+ if (this.config.enablePeripheral) {
168
+ await this.initPeripheral();
169
+ }
170
+
171
+ this.running = true;
172
+ this.emit('ready');
173
+ } catch (error) {
174
+ this.emit('error', error as Error, 'start');
175
+ throw error;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Stop the BLE transport
181
+ */
182
+ async stop(): Promise<void> {
183
+ if (!this.running) return;
184
+
185
+ try {
186
+ // Stop scanning
187
+ if (this.noble) {
188
+ await this.noble.stopScanningAsync();
189
+ }
190
+
191
+ // Stop advertising
192
+ if (this.bleno) {
193
+ this.bleno.stopAdvertising();
194
+ }
195
+
196
+ // Disconnect all peripherals
197
+ for (const [, conn] of this.peripherals) {
198
+ try {
199
+ await conn.peripheral.disconnectAsync();
200
+ } catch {
201
+ // Ignore disconnect errors
202
+ }
203
+ }
204
+
205
+ this.peripherals.clear();
206
+ this.links.clear();
207
+ this.subscribedCentrals.clear();
208
+ this.running = false;
209
+ } catch (error) {
210
+ this.emit('error', error as Error, 'stop');
211
+ throw error;
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Get all active links
217
+ */
218
+ getLinks(): BLELink[] {
219
+ return Array.from(this.links.values());
220
+ }
221
+
222
+ /**
223
+ * Get link by ID
224
+ */
225
+ getLink(id: string): BLELink | undefined {
226
+ return this.links.get(id);
227
+ }
228
+
229
+ /**
230
+ * Broadcast data to all connected peers
231
+ */
232
+ async broadcast(data: Uint8Array): Promise<void> {
233
+ // Send via central mode (noble) links
234
+ const sendPromises = Array.from(this.links.values()).map(async (link) => {
235
+ try {
236
+ await link.send(data);
237
+ } catch (error) {
238
+ this.emit('error', error as Error, `broadcast to ${link.id}`);
239
+ }
240
+ });
241
+
242
+ // Send via peripheral mode (bleno) notifications
243
+ if (this.notifyCallback) {
244
+ try {
245
+ console.log('[BLE] Notifying subscribed centrals,', data.length, 'bytes');
246
+ this.notifyCallback(Buffer.from(data));
247
+ } catch (error) {
248
+ console.error('[BLE] Notify error:', error);
249
+ this.emit('error', error as Error, 'notify');
250
+ }
251
+ } else {
252
+ console.log('[BLE] No notify callback (no centrals subscribed)');
253
+ }
254
+
255
+ await Promise.allSettled(sendPromises);
256
+ }
257
+
258
+ // --- Central Mode (Noble) ---
259
+
260
+ private async initCentral(): Promise<void> {
261
+ console.log('[BLE] Initializing central mode (noble)...');
262
+ const nobleModule = await import('@abandonware/noble');
263
+ this.noble = nobleModule.default;
264
+ console.log('[BLE] Noble imported, state:', (this.noble as any).state);
265
+
266
+ // Wait for powered on
267
+ await this.waitForPoweredOn();
268
+ console.log('[BLE] Noble powered on');
269
+
270
+ // Set up event handlers
271
+ this.noble.on('discover', (peripheral) => this.onDiscover(peripheral));
272
+ this.noble.on('stateChange', (state) => this.emit('state', state));
273
+
274
+ // Start scanning
275
+ console.log('[BLE] Starting scan for service:', this.config.serviceUUID);
276
+ await this.noble.startScanningAsync([this.config.serviceUUID], this.config.scanDuplicates);
277
+ console.log('[BLE] Scanning started');
278
+ }
279
+
280
+ private async waitForPoweredOn(): Promise<void> {
281
+ if (!this.noble) throw new Error('Noble not initialized');
282
+
283
+ const noble = this.noble as any;
284
+ if (noble.state === 'poweredOn') return;
285
+
286
+ return new Promise((resolve, reject) => {
287
+ const timeout = setTimeout(() => {
288
+ reject(new Error('Bluetooth adapter timeout'));
289
+ }, 10000);
290
+
291
+ const handler = (state: string) => {
292
+ if (state === 'poweredOn') {
293
+ clearTimeout(timeout);
294
+ noble.removeListener('stateChange', handler);
295
+ resolve();
296
+ } else if (state === 'poweredOff' || state === 'unauthorized') {
297
+ clearTimeout(timeout);
298
+ reject(new Error(`Bluetooth state: ${state}`));
299
+ }
300
+ };
301
+
302
+ noble.on('stateChange', handler);
303
+ });
304
+ }
305
+
306
+ // Track failed connection attempts for retry logic
307
+ private failedConnections = new Map<string, { count: number; lastAttempt: Date }>();
308
+ // Track peripherals currently being connected to (prevent duplicate attempts)
309
+ private connectingPeripherals = new Set<string>();
310
+
311
+ private async onDiscover(peripheral: Peripheral): Promise<void> {
312
+ // Skip verbose logging for repeated discoveries
313
+ const isRepeatedDiscovery =
314
+ this.failedConnections.has(peripheral.uuid) ||
315
+ this.peripherals.has(peripheral.uuid) ||
316
+ this.connectingPeripherals.has(peripheral.uuid);
317
+ if (!isRepeatedDiscovery) {
318
+ console.log(
319
+ '[BLE] Discovered peripheral:',
320
+ peripheral.uuid,
321
+ peripheral.advertisement?.localName
322
+ );
323
+ }
324
+
325
+ // Skip if already connected
326
+ if (this.peripherals.has(peripheral.uuid)) return;
327
+
328
+ // Skip if currently connecting (prevents duplicate connect attempts)
329
+ if (this.connectingPeripherals.has(peripheral.uuid)) return;
330
+
331
+ // If we have a working peripheral connection (someone subscribed to us),
332
+ // don't spam central connection attempts - mesh protocol doesn't need bidirectional
333
+ if (this.notifyCallback && this.links.size === 0) {
334
+ // We have a working inbound connection, don't need outbound
335
+ return;
336
+ }
337
+
338
+ // Check if we've failed too recently
339
+ const failed = this.failedConnections.get(peripheral.uuid);
340
+ if (failed) {
341
+ const backoffMs = Math.min(60000, 2000 * 2 ** failed.count); // Longer backoff
342
+ const elapsed = Date.now() - failed.lastAttempt.getTime();
343
+ if (elapsed < backoffMs) {
344
+ // Don't log every skip - too noisy
345
+ return;
346
+ }
347
+ }
348
+
349
+ // Mark as connecting to prevent duplicate attempts
350
+ this.connectingPeripherals.add(peripheral.uuid);
351
+
352
+ try {
353
+ console.log('[BLE] Connecting to', peripheral.uuid);
354
+ await peripheral.connectAsync();
355
+ console.log('[BLE] Connected to', peripheral.uuid);
356
+
357
+ // Clear tracking state on success
358
+ this.connectingPeripherals.delete(peripheral.uuid);
359
+ this.failedConnections.delete(peripheral.uuid);
360
+
361
+ // Discover services and characteristics
362
+ console.log('[BLE] Discovering services for', peripheral.uuid);
363
+ const { characteristics } = await peripheral.discoverSomeServicesAndCharacteristicsAsync(
364
+ [this.config.serviceUUID],
365
+ [this.config.characteristicUUID]
366
+ );
367
+ console.log('[BLE] Found', characteristics.length, 'characteristics');
368
+
369
+ if (characteristics.length === 0) {
370
+ console.log('[BLE] No characteristics found, disconnecting');
371
+ await peripheral.disconnectAsync();
372
+ return;
373
+ }
374
+
375
+ const characteristic = characteristics[0];
376
+
377
+ // Get MTU
378
+ const mtu = peripheral.mtu ?? BLE_MAX_MTU;
379
+ console.log('[BLE] Peripheral MTU:', peripheral.mtu, '(using:', `${mtu})`);
380
+
381
+ // Store connection
382
+ const conn: ConnectedPeripheral = {
383
+ peripheral,
384
+ characteristic,
385
+ mtu,
386
+ };
387
+ this.peripherals.set(peripheral.uuid, conn);
388
+
389
+ // Create link
390
+ const link = new BLELink(peripheral, characteristic, mtu);
391
+ this.links.set(peripheral.uuid, link);
392
+
393
+ // Subscribe to notifications
394
+ await characteristic.subscribeAsync();
395
+ characteristic.on('data', (data: Buffer) => {
396
+ this.emit('data', new Uint8Array(data), link);
397
+ });
398
+
399
+ // Handle disconnect
400
+ peripheral.once('disconnect', () => {
401
+ this.onDisconnect(peripheral.uuid);
402
+ });
403
+
404
+ this.emit('link:connected', link);
405
+ } catch (error) {
406
+ // Clear connecting state
407
+ this.connectingPeripherals.delete(peripheral.uuid);
408
+
409
+ const err = error as Error;
410
+
411
+ // Track failure for retry backoff
412
+ const failed = this.failedConnections.get(peripheral.uuid) ?? {
413
+ count: 0,
414
+ lastAttempt: new Date(),
415
+ };
416
+ failed.count++;
417
+ failed.lastAttempt = new Date();
418
+ this.failedConnections.set(peripheral.uuid, failed);
419
+
420
+ // Only log first few failures, then go quiet
421
+ if (failed.count <= 3) {
422
+ console.error(
423
+ '[BLE] Connection failed:',
424
+ err?.message ?? 'unknown error',
425
+ `(attempt ${failed.count})`
426
+ );
427
+ }
428
+
429
+ // Try to disconnect if still connected
430
+ try {
431
+ if (peripheral.state === 'connected' || peripheral.state === 'connecting') {
432
+ await peripheral.disconnectAsync();
433
+ }
434
+ } catch {}
435
+
436
+ // Only emit error for first failure
437
+ if (failed.count === 1) {
438
+ this.emit('error', err ?? new Error('Unknown BLE discover error'), 'discover');
439
+ }
440
+ }
441
+ }
442
+
443
+ private onDisconnect(peripheralUUID: string): void {
444
+ this.peripherals.delete(peripheralUUID);
445
+ this.links.delete(peripheralUUID);
446
+ this.emit('link:disconnected', peripheralUUID);
447
+ }
448
+
449
+ // --- Peripheral Mode (Bleno) ---
450
+
451
+ private async initPeripheral(): Promise<void> {
452
+ try {
453
+ const blenoModule = await import('@abandonware/bleno');
454
+ this.bleno = blenoModule.default;
455
+
456
+ // Wait for powered on
457
+ await this.waitForBlenoPoweredOn();
458
+
459
+ // Create characteristic
460
+ const CharacteristicClass = this.bleno.Characteristic;
461
+ const characteristic = new CharacteristicClass({
462
+ uuid: this.config.characteristicUUID.replace(/-/g, ''),
463
+ properties: ['read', 'write', 'writeWithoutResponse', 'notify'],
464
+ onWriteRequest: (
465
+ data: Buffer,
466
+ _offset: number,
467
+ _withoutResponse: boolean,
468
+ callback: (result: number) => void
469
+ ) => {
470
+ // Handle incoming data from central
471
+ this.emit('data', new Uint8Array(data), null as any);
472
+ callback(CharacteristicClass.RESULT_SUCCESS);
473
+ },
474
+ onSubscribe: (_maxValueSize: number, updateValueCallback: (data: Buffer) => void) => {
475
+ // Central subscribed to notifications - store callback
476
+ this.notifyCallback = updateValueCallback;
477
+ console.log('Central subscribed to notifications');
478
+ },
479
+ onUnsubscribe: () => {
480
+ // Central unsubscribed
481
+ this.notifyCallback = undefined;
482
+ console.log('Central unsubscribed');
483
+ },
484
+ });
485
+
486
+ // Create service
487
+ const PrimaryServiceClass = this.bleno.PrimaryService;
488
+ const service = new PrimaryServiceClass({
489
+ uuid: this.config.serviceUUID.replace(/-/g, ''),
490
+ characteristics: [characteristic],
491
+ });
492
+
493
+ // Set services
494
+ this.bleno.setServices([service]);
495
+
496
+ // Listen for central connection events
497
+ const bleno = this.bleno as any;
498
+ bleno.on('accept', (clientAddress: string) => {
499
+ console.log('[BLE] Central accepted connection from:', clientAddress);
500
+ });
501
+ bleno.on('disconnect', (clientAddress: string) => {
502
+ console.log('[BLE] Central disconnected:', clientAddress);
503
+ });
504
+ bleno.on('servicesSet', (error?: Error) => {
505
+ console.log('[BLE] Services set, error:', error);
506
+ });
507
+ bleno.on('advertisingStart', (error?: Error) => {
508
+ console.log('[BLE] Advertising started, error:', error);
509
+ });
510
+
511
+ // Start advertising
512
+ const name = this.config.advertisingName ?? 'bitchat';
513
+ this.bleno.startAdvertising(name, [this.config.serviceUUID]);
514
+ } catch (error) {
515
+ // Bleno might not be available on all platforms
516
+ console.warn('Bleno (peripheral mode) not available:', error);
517
+ }
518
+ }
519
+
520
+ private async waitForBlenoPoweredOn(): Promise<void> {
521
+ if (!this.bleno) throw new Error('Bleno not initialized');
522
+
523
+ const bleno = this.bleno as any;
524
+ if (bleno.state === 'poweredOn') return;
525
+
526
+ return new Promise((resolve, reject) => {
527
+ const timeout = setTimeout(() => {
528
+ reject(new Error('Bleno timeout'));
529
+ }, 10000);
530
+
531
+ const handler = (state: string) => {
532
+ if (state === 'poweredOn') {
533
+ clearTimeout(timeout);
534
+ bleno.removeListener('stateChange', handler);
535
+ resolve();
536
+ } else if (state === 'poweredOff' || state === 'unauthorized') {
537
+ clearTimeout(timeout);
538
+ reject(new Error(`Bleno state: ${state}`));
539
+ }
540
+ };
541
+
542
+ bleno.on('stateChange', handler);
543
+ });
544
+ }
545
+
546
+ /**
547
+ * Clean up
548
+ */
549
+ destroy(): void {
550
+ this.stop().catch(() => {});
551
+ this.removeAllListeners();
552
+ }
553
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Transport module exports
3
+ */
4
+
5
+ export {
6
+ BLELink,
7
+ BLETransport,
8
+ type BLETransportConfig,
9
+ type BLETransportEvents,
10
+ } from './ble.js';
@@ -0,0 +1 @@
1
+ export { startUIServer, type UIServerConfig } from './server.js';