bitchat-node 0.1.0 → 0.1.2

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 (45) hide show
  1. package/README.md +94 -23
  2. package/dist/client.d.ts +1 -0
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +143 -28
  5. package/dist/client.js.map +1 -1
  6. package/dist/debug.d.ts +39 -0
  7. package/dist/debug.d.ts.map +1 -0
  8. package/dist/debug.js +89 -0
  9. package/dist/debug.js.map +1 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/mesh/router.d.ts.map +1 -1
  15. package/dist/mesh/router.js +9 -1
  16. package/dist/mesh/router.js.map +1 -1
  17. package/dist/protocol/binary.d.ts +1 -1
  18. package/dist/protocol/binary.d.ts.map +1 -1
  19. package/dist/protocol/binary.js +2 -2
  20. package/dist/protocol/binary.js.map +1 -1
  21. package/dist/session/manager.d.ts.map +1 -1
  22. package/dist/session/manager.js +18 -1
  23. package/dist/session/manager.js.map +1 -1
  24. package/dist/transport/ble.d.ts +15 -0
  25. package/dist/transport/ble.d.ts.map +1 -1
  26. package/dist/transport/ble.js +63 -16
  27. package/dist/transport/ble.js.map +1 -1
  28. package/dist/ui/html.d.ts +5 -0
  29. package/dist/ui/html.d.ts.map +1 -0
  30. package/dist/ui/html.js +506 -0
  31. package/dist/ui/html.js.map +1 -0
  32. package/dist/ui/server.d.ts +5 -1
  33. package/dist/ui/server.d.ts.map +1 -1
  34. package/dist/ui/server.js +61 -255
  35. package/dist/ui/server.js.map +1 -1
  36. package/package.json +3 -2
  37. package/src/client.ts +159 -34
  38. package/src/debug.ts +119 -0
  39. package/src/index.ts +11 -0
  40. package/src/mesh/router.ts +11 -1
  41. package/src/protocol/binary.ts +2 -2
  42. package/src/session/manager.ts +19 -1
  43. package/src/transport/ble.ts +70 -16
  44. package/src/ui/html.ts +506 -0
  45. package/src/ui/server.ts +78 -258
package/src/ui/server.ts CHANGED
@@ -1,257 +1,14 @@
1
1
  /**
2
2
  * Bitchat Web UI Server
3
- * Simple HTTP + WebSocket server for testing
3
+ * Enhanced with debug panel and session monitoring
4
4
  */
5
5
 
6
6
  import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
7
7
  import { WebSocket, WebSocketServer } from 'ws';
8
8
  import type { BitchatClient } from '../client.js';
9
9
  import type { ChatMessage, PeerInfo } from '../protocol/types.js';
10
-
11
- const HTML = `<!DOCTYPE html>
12
- <html>
13
- <head>
14
- <meta charset="UTF-8">
15
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
16
- <title>Bitchat Node</title>
17
- <style>
18
- * { box-sizing: border-box; margin: 0; padding: 0; }
19
- body {
20
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
21
- background: #1a1a2e; color: #eee; height: 100vh; display: flex; flex-direction: column;
22
- }
23
- header {
24
- background: #16213e; padding: 16px 20px; border-bottom: 1px solid #0f3460;
25
- display: flex; justify-content: space-between; align-items: center;
26
- }
27
- header h1 { font-size: 18px; font-weight: 600; }
28
- .status { font-size: 12px; color: #888; }
29
- .status.connected { color: #4ade80; }
30
- .peers { font-size: 12px; color: #60a5fa; margin-left: 16px; }
31
- main { flex: 1; display: flex; overflow: hidden; }
32
- .messages {
33
- flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 8px;
34
- }
35
- .message {
36
- background: #16213e; padding: 12px 16px; border-radius: 12px; max-width: 80%;
37
- animation: fadeIn 0.2s ease;
38
- }
39
- @keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } }
40
- .message.mine { background: #0f3460; align-self: flex-end; }
41
- .message.private { border-left: 3px solid #f472b6; }
42
- .message .meta { font-size: 11px; color: #888; margin-bottom: 4px; }
43
- .message .meta .nickname { color: #60a5fa; font-weight: 500; }
44
- .message .meta .private-badge { color: #f472b6; margin-left: 8px; }
45
- .message .content { line-height: 1.4; word-wrap: break-word; }
46
- .system {
47
- text-align: center; font-size: 12px; color: #666; padding: 8px;
48
- }
49
- footer { background: #16213e; padding: 16px; border-top: 1px solid #0f3460; }
50
- .input-row { display: flex; gap: 8px; }
51
- input[type="text"] {
52
- flex: 1; background: #1a1a2e; border: 1px solid #0f3460; border-radius: 8px;
53
- padding: 12px 16px; color: #eee; font-size: 14px; outline: none;
54
- }
55
- input[type="text"]:focus { border-color: #3b82f6; }
56
- input[type="text"]::placeholder { color: #555; }
57
- button {
58
- background: #3b82f6; color: white; border: none; border-radius: 8px;
59
- padding: 12px 24px; font-size: 14px; font-weight: 500; cursor: pointer;
60
- transition: background 0.2s;
61
- }
62
- button:hover { background: #2563eb; }
63
- button:disabled { background: #374151; cursor: not-allowed; }
64
- .peers-panel {
65
- width: 200px; background: #16213e; border-left: 1px solid #0f3460;
66
- padding: 16px; overflow-y: auto;
67
- }
68
- .peers-panel h3 { font-size: 12px; color: #888; margin-bottom: 12px; text-transform: uppercase; }
69
- .peer {
70
- padding: 8px 12px; background: #1a1a2e; border-radius: 6px; margin-bottom: 8px;
71
- font-size: 13px; cursor: pointer; transition: background 0.2s;
72
- }
73
- .peer:hover { background: #0f3460; }
74
- .peer .peer-id { font-size: 10px; color: #666; margin-top: 2px; font-family: monospace; }
75
- .peer.selected { background: #3b82f6; }
76
- </style>
77
- </head>
78
- <body>
79
- <header>
80
- <div>
81
- <h1>🔗 Bitchat Node</h1>
82
- <span class="status" id="status">Connecting...</span>
83
- <span class="peers" id="peer-count"></span>
84
- </div>
85
- <div id="my-info" style="font-size: 12px; color: #888;"></div>
86
- </header>
87
- <main>
88
- <div class="messages" id="messages"></div>
89
- <div class="peers-panel">
90
- <h3>Peers</h3>
91
- <div id="peers-list"></div>
92
- </div>
93
- </main>
94
- <footer>
95
- <div class="input-row">
96
- <input type="text" id="input" placeholder="Type a message..." autocomplete="off">
97
- <button id="send">Send</button>
98
- </div>
99
- </footer>
100
- <script>
101
- const messagesEl = document.getElementById('messages');
102
- const inputEl = document.getElementById('input');
103
- const sendBtn = document.getElementById('send');
104
- const statusEl = document.getElementById('status');
105
- const peerCountEl = document.getElementById('peer-count');
106
- const peersListEl = document.getElementById('peers-list');
107
- const myInfoEl = document.getElementById('my-info');
108
-
109
- let ws;
110
- let myPeerID = '';
111
- let selectedPeer = null;
112
- const peers = new Map();
113
-
114
- function connect() {
115
- ws = new WebSocket('ws://' + location.host + '/ws');
116
-
117
- ws.onopen = () => {
118
- statusEl.textContent = 'Connected';
119
- statusEl.className = 'status connected';
120
- };
121
-
122
- ws.onclose = () => {
123
- statusEl.textContent = 'Disconnected';
124
- statusEl.className = 'status';
125
- setTimeout(connect, 2000);
126
- };
127
-
128
- ws.onmessage = (e) => {
129
- const msg = JSON.parse(e.data);
130
- handleMessage(msg);
131
- };
132
- }
133
-
134
- function handleMessage(msg) {
135
- switch (msg.type) {
136
- case 'init':
137
- myPeerID = msg.peerID;
138
- myInfoEl.textContent = msg.nickname + ' · ' + msg.peerID.slice(0, 8) + '...';
139
- break;
140
-
141
- case 'message':
142
- addMessage(msg.message);
143
- break;
144
-
145
- case 'peer:connected':
146
- peers.set(msg.peer.peerID, msg.peer);
147
- updatePeersList();
148
- addSystem(msg.peer.nickname + ' joined the mesh');
149
- break;
150
-
151
- case 'peer:disconnected':
152
- const peer = peers.get(msg.peerID);
153
- peers.delete(msg.peerID);
154
- updatePeersList();
155
- if (peer) addSystem(peer.nickname + ' left the mesh');
156
- break;
157
-
158
- case 'peers':
159
- peers.clear();
160
- msg.peers.forEach(p => peers.set(p.peerID, p));
161
- updatePeersList();
162
- break;
163
-
164
- case 'sent':
165
- // Could update delivery status
166
- break;
167
-
168
- case 'error':
169
- addSystem('Error: ' + msg.error);
170
- break;
171
- }
172
- }
173
-
174
- function addMessage(msg) {
175
- const div = document.createElement('div');
176
- div.className = 'message' + (msg.sender === myPeerID ? ' mine' : '') + (msg.isPrivate ? ' private' : '');
177
- div.innerHTML =
178
- '<div class="meta">' +
179
- '<span class="nickname">' + escapeHtml(msg.senderNickname) + '</span> · ' +
180
- new Date(msg.timestamp).toLocaleTimeString() +
181
- (msg.isPrivate ? '<span class="private-badge">private</span>' : '') +
182
- '</div>' +
183
- '<div class="content">' + escapeHtml(msg.content) + '</div>';
184
- messagesEl.appendChild(div);
185
- messagesEl.scrollTop = messagesEl.scrollHeight;
186
- }
187
-
188
- function addSystem(text) {
189
- const div = document.createElement('div');
190
- div.className = 'system';
191
- div.textContent = text;
192
- messagesEl.appendChild(div);
193
- messagesEl.scrollTop = messagesEl.scrollHeight;
194
- }
195
-
196
- function updatePeersList() {
197
- peerCountEl.textContent = peers.size + ' peer' + (peers.size !== 1 ? 's' : '');
198
- peersListEl.innerHTML = '';
199
- peers.forEach((peer, id) => {
200
- const div = document.createElement('div');
201
- div.className = 'peer' + (selectedPeer === id ? ' selected' : '');
202
- div.innerHTML =
203
- '<div>' + escapeHtml(peer.nickname) + '</div>' +
204
- '<div class="peer-id">' + id.slice(0, 12) + '...</div>';
205
- div.onclick = () => {
206
- selectedPeer = selectedPeer === id ? null : id;
207
- updatePeersList();
208
- inputEl.placeholder = selectedPeer ? 'Private message to ' + peer.nickname + '...' : 'Type a message...';
209
- };
210
- peersListEl.appendChild(div);
211
- });
212
- }
213
-
214
- function send() {
215
- const text = inputEl.value.trim();
216
- if (!text) return;
217
-
218
- if (!ws || ws.readyState !== WebSocket.OPEN) {
219
- addSystem('Not connected - please wait');
220
- return;
221
- }
222
-
223
- // Show our own message immediately
224
- addMessage({
225
- id: Date.now().toString(),
226
- sender: myPeerID,
227
- senderNickname: 'me',
228
- content: text,
229
- timestamp: new Date().toISOString(),
230
- isPrivate: !!selectedPeer
231
- });
232
-
233
- ws.send(JSON.stringify({
234
- type: 'send',
235
- text: text,
236
- to: selectedPeer || null
237
- }));
238
-
239
- inputEl.value = '';
240
- }
241
-
242
- function escapeHtml(text) {
243
- const div = document.createElement('div');
244
- div.textContent = text;
245
- return div.innerHTML;
246
- }
247
-
248
- sendBtn.onclick = send;
249
- inputEl.onkeydown = (e) => { if (e.key === 'Enter') send(); };
250
-
251
- connect();
252
- </script>
253
- </body>
254
- </html>`;
10
+ import { HTML } from './html.js';
11
+ import { debugLog, type DebugEvent, type SessionState } from '../debug.js';
255
12
 
256
13
  export interface UIServerConfig {
257
14
  port: number;
@@ -268,10 +25,17 @@ interface StoredMessage {
268
25
  timestamp: number;
269
26
  }
270
27
 
28
+ // Re-export for convenience
29
+ export type { DebugEvent, SessionState } from '../debug.js';
30
+
271
31
  /**
272
32
  * Start a web UI server for the Bitchat client
273
33
  */
274
- export function startUIServer(client: BitchatClient, config: UIServerConfig): { stop: () => void } {
34
+ export function startUIServer(client: BitchatClient, config: UIServerConfig): {
35
+ stop: () => void;
36
+ emitDebug: (event: DebugEvent) => void;
37
+ updateSession: (state: SessionState) => void;
38
+ } {
275
39
  const { port } = config;
276
40
  const clients = new Set<WebSocket>();
277
41
 
@@ -279,6 +43,13 @@ export function startUIServer(client: BitchatClient, config: UIServerConfig): {
279
43
  const messageStore: StoredMessage[] = [];
280
44
  const MAX_MESSAGES = 100;
281
45
 
46
+ // Debug log store (keep last 500 entries)
47
+ const debugStore: DebugEvent[] = [];
48
+ const MAX_DEBUG = 500;
49
+
50
+ // Session states
51
+ const sessionStates = new Map<string, SessionState>();
52
+
282
53
  // Registered webhooks
283
54
  const webhooks: string[] = [];
284
55
  if (config.webhookUrl) {
@@ -322,6 +93,35 @@ export function startUIServer(client: BitchatClient, config: UIServerConfig): {
322
93
  }
323
94
  };
324
95
 
96
+ // Broadcast to all connected WebSocket clients
97
+ const broadcast = (msg: object) => {
98
+ const data = JSON.stringify(msg);
99
+ clients.forEach((ws) => {
100
+ if (ws.readyState === WebSocket.OPEN) {
101
+ ws.send(data);
102
+ }
103
+ });
104
+ };
105
+
106
+ // Emit debug event to UI
107
+ const emitDebug = (event: DebugEvent) => {
108
+ debugStore.push(event);
109
+ if (debugStore.length > MAX_DEBUG) {
110
+ debugStore.shift();
111
+ }
112
+ broadcast({ type: 'debug', ...event });
113
+ };
114
+
115
+ // Update session state in UI
116
+ const updateSession = (state: SessionState) => {
117
+ sessionStates.set(state.peerID, state);
118
+ broadcast({ type: 'session', ...state });
119
+ };
120
+
121
+ // Connect to global debug logger
122
+ debugLog.on('debug', emitDebug);
123
+ debugLog.on('session', updateSession);
124
+
325
125
  // HTTP server with REST API
326
126
  const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
327
127
  const url = new URL(req.url ?? '/', `http://localhost:${port}`);
@@ -357,7 +157,7 @@ export function startUIServer(client: BitchatClient, config: UIServerConfig): {
357
157
  peerID: p.peerID.toHex(),
358
158
  nickname: p.nickname,
359
159
  isConnected: p.isConnected,
360
- lastSeen: Date.now(), // TODO: track actual last seen
160
+ lastSeen: Date.now(),
361
161
  }));
362
162
  sendJson(res, 200, peers);
363
163
  return;
@@ -370,6 +170,18 @@ export function startUIServer(client: BitchatClient, config: UIServerConfig): {
370
170
  return;
371
171
  }
372
172
 
173
+ if (path === '/api/debug' && method === 'GET') {
174
+ const since = parseInt(url.searchParams.get('since') ?? '0', 10);
175
+ const events = debugStore.filter((e) => e.time > since);
176
+ sendJson(res, 200, events);
177
+ return;
178
+ }
179
+
180
+ if (path === '/api/sessions' && method === 'GET') {
181
+ sendJson(res, 200, Array.from(sessionStates.values()));
182
+ return;
183
+ }
184
+
373
185
  if (path === '/api/send' && method === 'POST') {
374
186
  const body = await parseBody(req);
375
187
  const { type, text, recipientPeerID } = body as {
@@ -460,6 +272,11 @@ export function startUIServer(client: BitchatClient, config: UIServerConfig): {
460
272
  })
461
273
  );
462
274
 
275
+ // Send current sessions
276
+ sessionStates.forEach((state) => {
277
+ ws.send(JSON.stringify({ type: 'session', ...state }));
278
+ });
279
+
463
280
  ws.on('message', async (data: Buffer) => {
464
281
  console.log('[WS] Received:', data.toString().slice(0, 100));
465
282
  try {
@@ -481,6 +298,11 @@ export function startUIServer(client: BitchatClient, config: UIServerConfig): {
481
298
  } catch (error) {
482
299
  console.error('[WS] Send error:', (error as Error).message);
483
300
  ws.send(JSON.stringify({ type: 'error', error: (error as Error).message }));
301
+ emitDebug({
302
+ category: 'error',
303
+ text: (error as Error).message,
304
+ time: Date.now(),
305
+ });
484
306
  }
485
307
  });
486
308
 
@@ -491,15 +313,6 @@ export function startUIServer(client: BitchatClient, config: UIServerConfig): {
491
313
  });
492
314
 
493
315
  // Forward client events to WebSocket clients
494
- const broadcast = (msg: object) => {
495
- const data = JSON.stringify(msg);
496
- clients.forEach((ws) => {
497
- if (ws.readyState === WebSocket.OPEN) {
498
- ws.send(data);
499
- }
500
- });
501
- };
502
-
503
316
  client.on('message', (message: ChatMessage) => {
504
317
  // Store message for polling
505
318
  const storedMessage: StoredMessage = {
@@ -513,7 +326,7 @@ export function startUIServer(client: BitchatClient, config: UIServerConfig): {
513
326
 
514
327
  messageStore.push(storedMessage);
515
328
  if (messageStore.length > MAX_MESSAGES) {
516
- messageStore.shift(); // Remove oldest
329
+ messageStore.shift();
517
330
  }
518
331
 
519
332
  // Notify webhooks
@@ -554,16 +367,23 @@ export function startUIServer(client: BitchatClient, config: UIServerConfig): {
554
367
  type: 'peer:disconnected',
555
368
  peerID: peerID.toHex(),
556
369
  });
370
+ // Remove session state
371
+ sessionStates.delete(peerID.toHex());
557
372
  });
558
373
 
559
374
  server.listen(port, () => {
560
375
  console.log(`Bitchat UI: http://localhost:${port}`);
376
+ console.log(`Debug panel: http://localhost:${port} → Debug tab`);
561
377
  });
562
378
 
563
379
  return {
564
380
  stop: () => {
381
+ debugLog.off('debug', emitDebug);
382
+ debugLog.off('session', updateSession);
565
383
  wss.close();
566
384
  server.close();
567
385
  },
386
+ emitDebug,
387
+ updateSession,
568
388
  };
569
389
  }