@dainprotocol/tunnel 1.1.7 → 1.1.9

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.
@@ -136,7 +136,6 @@ class DainTunnel extends events_1.EventEmitter {
136
136
  });
137
137
  }
138
138
  handleMessage(message, resolve) {
139
- console.log(`[Tunnel Client] Received message type: ${message.type}`);
140
139
  switch (message.type) {
141
140
  case "tunnelUrl":
142
141
  this.tunnelUrl = message.url;
@@ -150,18 +149,14 @@ class DainTunnel extends events_1.EventEmitter {
150
149
  this.handleWebSocketConnection(message);
151
150
  break;
152
151
  case "sse_connection":
153
- console.log(`[Tunnel Client] Routing to handleSSEConnection`);
154
152
  this.handleSSEConnection(message);
155
153
  break;
156
154
  case "sse_close":
157
- console.log(`[Tunnel Client] Routing to handleSSEClose`);
158
155
  this.handleSSEClose(message);
159
156
  break;
160
157
  case "websocket":
161
158
  this.handleWebSocketMessage(message);
162
159
  break;
163
- default:
164
- console.log(`[Tunnel Client] Unknown message type: ${message.type}`);
165
160
  }
166
161
  }
167
162
  async handleRequest(request) {
@@ -255,11 +250,6 @@ class DainTunnel extends events_1.EventEmitter {
255
250
  }
256
251
  }
257
252
  handleSSEConnection(message) {
258
- console.log(`[Tunnel Client] ====== SSE CONNECTION HANDLER START ======`);
259
- console.log(`[Tunnel Client] SSE ID: ${message.id}`);
260
- console.log(`[Tunnel Client] Path: ${message.path}`);
261
- console.log(`[Tunnel Client] Method: ${message.method}`);
262
- console.log(`[Tunnel Client] Local port: ${this.port}`);
263
253
  try {
264
254
  // Create an EventSource-like stream to the local server
265
255
  // Since Node.js doesn't have a built-in EventSource, we'll use HTTP
@@ -270,13 +260,8 @@ class DainTunnel extends events_1.EventEmitter {
270
260
  method: message.method || 'GET', // Use provided method (supports POST tool calls)
271
261
  headers: message.headers,
272
262
  };
273
- console.log(`[Tunnel Client] Creating HTTP request to: http://localhost:${this.port}${message.path}`);
274
- console.log(`[Tunnel Client] Request headers:`, JSON.stringify(message.headers, null, 2));
275
263
  const req = http_1.default.request(options, (res) => {
276
- console.log(`[Tunnel Client] Response received with status: ${res.statusCode}`);
277
- console.log(`[Tunnel Client] Response headers:`, JSON.stringify(res.headers, null, 2));
278
264
  if (res.statusCode !== 200) {
279
- console.error(`[Tunnel Client] ❌ Non-200 status code: ${res.statusCode}`);
280
265
  if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
281
266
  this.ws.send(JSON.stringify({
282
267
  type: 'sse',
@@ -287,22 +272,17 @@ class DainTunnel extends events_1.EventEmitter {
287
272
  }
288
273
  return;
289
274
  }
290
- console.log(`[Tunnel Client] ✓ Starting SSE stream processing for ${message.id}`);
291
275
  // Process SSE stream
292
276
  let buffer = '';
293
- let eventCount = 0;
294
277
  res.on('data', (chunk) => {
295
278
  const chunkStr = chunk.toString();
296
- console.log(`[Tunnel Client] SSE chunk ${++eventCount} received (${chunkStr.length} bytes)`);
297
- console.log(`[Tunnel Client] Chunk preview:`, chunkStr.substring(0, 200));
279
+ console.log(`[Tunnel Client] SSE chunk received (${chunkStr.length} bytes):`, chunkStr.substring(0, 200));
298
280
  buffer += chunkStr;
299
281
  // Process complete SSE messages
300
- let messagesProcessed = 0;
301
282
  while (buffer.includes('\n\n')) {
302
283
  const messageEndIndex = buffer.indexOf('\n\n');
303
284
  const messageData = buffer.substring(0, messageEndIndex);
304
285
  buffer = buffer.substring(messageEndIndex + 2);
305
- console.log(`[Tunnel Client] Processing SSE message ${++messagesProcessed}:`, messageData);
306
286
  // Parse the SSE message
307
287
  const lines = messageData.split('\n');
308
288
  let event = 'message';
@@ -320,27 +300,18 @@ class DainTunnel extends events_1.EventEmitter {
320
300
  data = data.substring(0, data.length - 1);
321
301
  }
322
302
  // Forward to server
323
- console.log(`[Tunnel Client] Forwarding SSE event to tunnel server:`);
324
- console.log(`[Tunnel Client] SSE ID: ${message.id}`);
325
- console.log(`[Tunnel Client] Event: ${event}`);
326
- console.log(`[Tunnel Client] Data length: ${data.length}`);
303
+ console.log(`[Tunnel Client] Forwarding SSE event: ${event}, data length: ${data.length}`);
327
304
  if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
328
- const forwardMessage = {
305
+ this.ws.send(JSON.stringify({
329
306
  type: 'sse',
330
307
  id: message.id,
331
308
  event,
332
309
  data
333
- };
334
- this.ws.send(JSON.stringify(forwardMessage));
335
- console.log(`[Tunnel Client] ✓ SSE event forwarded to tunnel server`);
336
- }
337
- else {
338
- console.error(`[Tunnel Client] ❌ Cannot forward SSE event - WebSocket not open`);
310
+ }));
339
311
  }
340
312
  }
341
313
  });
342
314
  res.on('end', () => {
343
- console.log(`[Tunnel Client] SSE stream ended for ${message.id}`);
344
315
  if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
345
316
  this.ws.send(JSON.stringify({
346
317
  type: 'sse',
@@ -348,15 +319,10 @@ class DainTunnel extends events_1.EventEmitter {
348
319
  event: 'close',
349
320
  data: 'Connection closed'
350
321
  }));
351
- console.log(`[Tunnel Client] Sent close event to tunnel server`);
352
322
  }
353
323
  });
354
- res.on('error', (error) => {
355
- console.error(`[Tunnel Client] Response stream error for ${message.id}:`, error);
356
- });
357
324
  });
358
325
  req.on('error', (error) => {
359
- console.error(`[Tunnel Client] ❌ Request error for ${message.id}:`, error);
360
326
  if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
361
327
  this.ws.send(JSON.stringify({
362
328
  type: 'sse',
@@ -368,20 +334,15 @@ class DainTunnel extends events_1.EventEmitter {
368
334
  });
369
335
  // Write request body for POST requests
370
336
  if (message.body && message.method !== 'GET') {
371
- const bodyBuffer = Buffer.from(message.body, 'base64');
372
- console.log(`[Tunnel Client] Writing request body (${bodyBuffer.length} bytes)`);
373
- req.write(bodyBuffer);
337
+ req.write(Buffer.from(message.body, 'base64'));
374
338
  }
375
339
  req.end();
376
- console.log(`[Tunnel Client] Request sent to local service`);
377
340
  // Store a reference to abort the connection later if needed
378
341
  this.sseClients.set(message.id, req);
379
- console.log(`[Tunnel Client] SSE client stored. Total SSE clients: ${this.sseClients.size}`);
380
342
  this.emit("sse_connection", { id: message.id, path: message.path });
381
- console.log(`[Tunnel Client] ====== SSE CONNECTION HANDLER COMPLETE ======`);
382
343
  }
383
344
  catch (error) {
384
- console.error(`[Tunnel Client] CRITICAL ERROR establishing SSE connection:`, error);
345
+ console.error('Error establishing SSE connection:', error);
385
346
  if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
386
347
  this.ws.send(JSON.stringify({
387
348
  type: 'sse',
@@ -393,17 +354,11 @@ class DainTunnel extends events_1.EventEmitter {
393
354
  }
394
355
  }
395
356
  handleSSEClose(message) {
396
- console.log(`[Tunnel Client] SSE close requested for: ${message.id}`);
397
357
  const client = this.sseClients.get(message.id);
398
358
  if (client) {
399
- console.log(`[Tunnel Client] Destroying SSE client: ${message.id}`);
400
359
  // Abort the request if it's still active
401
360
  client.destroy();
402
361
  this.sseClients.delete(message.id);
403
- console.log(`[Tunnel Client] SSE client destroyed. Remaining: ${this.sseClients.size}`);
404
- }
405
- else {
406
- console.log(`[Tunnel Client] SSE client not found: ${message.id}`);
407
362
  }
408
363
  }
409
364
  forwardRequest(request) {
@@ -12,6 +12,31 @@ const cors_1 = __importDefault(require("cors"));
12
12
  const auth_1 = require("@dainprotocol/service-sdk/service/auth");
13
13
  const crypto_1 = require("crypto");
14
14
  const url_1 = require("url");
15
+ /**
16
+ * Force immediate flush of SSE data to prevent buffering
17
+ * SSE requires immediate delivery; Node.js res.write() doesn't guarantee this
18
+ *
19
+ * @param res Express response object
20
+ */
21
+ function flushSSE(res) {
22
+ try {
23
+ // Access underlying socket to force flush
24
+ // Type assertion needed as Express types don't expose socket methods
25
+ const socket = res.socket;
26
+ if (socket && typeof socket.flush === 'function') {
27
+ socket.flush();
28
+ }
29
+ // Fallback: try connection property (older Node.js versions)
30
+ const connection = res.connection;
31
+ if (connection && typeof connection.flush === 'function') {
32
+ connection.flush();
33
+ }
34
+ }
35
+ catch (error) {
36
+ // Flush is best-effort; log but don't fail the request
37
+ console.error('[SSE Flush] Error flushing socket:', error);
38
+ }
39
+ }
15
40
  class DainTunnelServer {
16
41
  constructor(hostname, port) {
17
42
  this.hostname = hostname;
@@ -300,55 +325,46 @@ class DainTunnelServer {
300
325
  }
301
326
  }
302
327
  handleSSEMessage(data) {
303
- var _a;
304
- console.log(`[Tunnel Server] ====== RECEIVED SSE MESSAGE ======`);
305
- console.log(`[Tunnel Server] SSE ID: ${data.id}`);
306
- console.log(`[Tunnel Server] Event: ${data.event}`);
307
- console.log(`[Tunnel Server] Data length: ${((_a = data.data) === null || _a === void 0 ? void 0 : _a.length) || 0}`);
308
- console.log(`[Tunnel Server] Active SSE connections: ${this.sseConnections.size}`);
309
- console.log(`[Tunnel Server] Active SSE IDs: ${Array.from(this.sseConnections.keys()).join(', ')}`);
310
328
  const connection = this.sseConnections.get(data.id);
311
329
  if (!connection) {
312
- console.error(`[Tunnel Server] ❌ SSE connection not found: ${data.id}`);
313
- console.error(`[Tunnel Server] Available connections: ${Array.from(this.sseConnections.keys()).join(', ')}`);
314
- console.error(`[Tunnel Server] This means either:`);
315
- console.error(`[Tunnel Server] 1. The connection was never created (handleSSERequest not called)`);
316
- console.error(`[Tunnel Server] 2. The connection was already closed`);
317
- console.error(`[Tunnel Server] 3. There's an ID mismatch`);
330
+ console.log(`SSE connection not found: ${data.id}`);
318
331
  return;
319
332
  }
320
- console.log(`[Tunnel Server] ✓ SSE connection found for ${data.id}`);
321
333
  const { res } = connection;
334
+ // Check if response is still writable before attempting to write
335
+ if (!res.writable) {
336
+ console.log(`SSE connection ${data.id} is no longer writable`);
337
+ this.sseConnections.delete(data.id);
338
+ return;
339
+ }
322
340
  try {
323
341
  if (data.event) {
324
342
  res.write(`event: ${data.event}\n`);
325
- console.log(`[Tunnel Server] Wrote event: ${data.event}`);
326
343
  }
327
344
  // Write data as-is (don't split - data is already properly formatted)
328
345
  res.write(`data: ${data.data}\n`);
329
346
  res.write('\n');
330
- console.log(`[Tunnel Server] Wrote ${data.data.length} bytes of data`);
347
+ // Force immediate flush to prevent buffering
348
+ // This is critical for SSE - without it, events may be delayed or lost
349
+ flushSSE(res);
331
350
  // Check if this is a "close" event
332
351
  if (data.event === 'close') {
333
- console.log(`[Tunnel Server] Close event received, ending SSE connection ${data.id}`);
334
- // Clear keepalive interval if it exists
335
- if (connection.keepaliveInterval) {
336
- clearInterval(connection.keepaliveInterval);
337
- console.log(`[Tunnel Server] Cleared keepalive interval for ${data.id}`);
338
- }
339
352
  res.end();
340
353
  this.sseConnections.delete(data.id);
341
- console.log(`[Tunnel Server] SSE connection closed. Remaining: ${this.sseConnections.size}`);
342
354
  }
343
- console.log(`[Tunnel Server] ====== SSE MESSAGE HANDLED ======`);
344
355
  }
345
356
  catch (error) {
346
- console.error(`[Tunnel Server] Error writing SSE message for ${data.id}:`, error);
347
- // Clear keepalive interval if it exists
348
- if (connection === null || connection === void 0 ? void 0 : connection.keepaliveInterval) {
349
- clearInterval(connection.keepaliveInterval);
350
- }
357
+ console.error(`Error writing SSE message for ${data.id}:`, error);
351
358
  this.sseConnections.delete(data.id);
359
+ // Best-effort cleanup: try to end the response
360
+ try {
361
+ if (res.writable) {
362
+ res.end();
363
+ }
364
+ }
365
+ catch (endError) {
366
+ console.error(`Error ending SSE connection ${data.id}:`, endError);
367
+ }
352
368
  }
353
369
  }
354
370
  handleWebSocketMessage(data) {
@@ -379,14 +395,9 @@ class DainTunnelServer {
379
395
  }
380
396
  async handleRequest(req, res) {
381
397
  const tunnelId = req.params.tunnelId;
382
- // Log incoming request with headers for debugging
383
- console.log(`[Tunnel Server] Incoming request: ${req.method} ${req.url} to tunnel ${tunnelId}`);
384
- console.log(`[Tunnel Server] Accept header: "${req.headers.accept}"`);
385
- console.log(`[Tunnel Server] Content-Type: "${req.headers['content-type']}"`);
386
398
  // Check for upgraded connections (WebSockets) - these are handled by the WebSocket server
387
399
  if (req.headers.upgrade && req.headers.upgrade.toLowerCase() === 'websocket') {
388
400
  // This is handled by the WebSocket server now
389
- console.log(`[Tunnel Server] WebSocket upgrade detected, skipping`);
390
401
  return;
391
402
  }
392
403
  let tunnel;
@@ -404,12 +415,8 @@ class DainTunnelServer {
404
415
  console.log(`Tunnel not found after retries: ${tunnelId}`);
405
416
  return res.status(404).send("Tunnel not found");
406
417
  }
407
- // Check for SSE request - more robust check
408
- const acceptHeader = (req.headers.accept || '').toLowerCase();
409
- const isSSE = acceptHeader.includes('text/event-stream');
410
- console.log(`[Tunnel Server] Is SSE request: ${isSSE} (accept: "${acceptHeader}")`);
411
- if (isSSE) {
412
- console.log(`[Tunnel Server] Routing to SSE handler`);
418
+ // Check for SSE request
419
+ if (req.headers.accept && req.headers.accept.includes('text/event-stream')) {
413
420
  return this.handleSSERequest(req, res, tunnelId, tunnel);
414
421
  }
415
422
  // Handle regular HTTP request
@@ -432,118 +439,44 @@ class DainTunnelServer {
432
439
  handleSSERequest(req, res, tunnelId, tunnel) {
433
440
  // Setup SSE connection
434
441
  const sseId = (0, uuid_1.v4)();
435
- console.log(`[Tunnel Server] ====== SSE CONNECTION SETUP START ======`);
436
- console.log(`[Tunnel Server] SSE ID: ${sseId}`);
437
- console.log(`[Tunnel Server] Tunnel ID: ${tunnelId}`);
438
- console.log(`[Tunnel Server] Path: ${req.url}`);
439
- console.log(`[Tunnel Server] Method: ${req.method}`);
440
- try {
441
- // CRITICAL: Disable TCP buffering (Nagle's algorithm) to ensure immediate data sending
442
- // Without this, the initial keepalive may be buffered and not reach the browser,
443
- // causing it to close the connection immediately
444
- if (req.socket) {
445
- req.socket.setNoDelay(true);
446
- req.socket.setTimeout(0);
447
- console.log(`[Tunnel Server] Socket configured: noDelay=true, timeout=0 for ${sseId}`);
448
- }
449
- res.writeHead(200, {
450
- 'Content-Type': 'text/event-stream',
451
- 'Cache-Control': 'no-cache',
452
- 'Connection': 'keep-alive',
453
- 'X-Accel-Buffering': 'no', // Disable nginx buffering
454
- 'Content-Encoding': 'identity', // Prevent compression middleware from buffering
455
- 'Transfer-Encoding': 'chunked' // Ensure chunked encoding for streaming
456
- });
457
- console.log(`[Tunnel Server] SSE headers written for ${sseId}`);
458
- // Send initial connection event - MUST be sent immediately to prevent browser timeout
459
- res.write(': keepalive\n\n');
460
- // Force flush if available (some environments support this)
461
- if (typeof res.flush === 'function') {
462
- res.flush();
463
- console.log(`[Tunnel Server] Response flushed for ${sseId}`);
464
- }
465
- console.log(`[Tunnel Server] Initial keepalive sent for ${sseId}`);
466
- // Send keepalive comments every 5 seconds to prevent timeout
467
- const keepaliveInterval = setInterval(() => {
468
- if (this.sseConnections.has(sseId)) {
469
- try {
470
- res.write(': keepalive\n\n');
471
- console.log(`[Tunnel Server] Keepalive sent for ${sseId}`);
472
- }
473
- catch (error) {
474
- console.error(`[Tunnel Server] Error sending keepalive for ${sseId}:`, error);
475
- clearInterval(keepaliveInterval);
476
- this.sseConnections.delete(sseId);
477
- }
478
- }
479
- else {
480
- clearInterval(keepaliveInterval);
481
- }
482
- }, 5000);
483
- // Store the SSE connection BEFORE notifying tunnel client
484
- this.sseConnections.set(sseId, {
485
- req,
486
- res,
487
- id: sseId,
488
- tunnelId,
489
- keepaliveInterval
490
- });
491
- console.log(`[Tunnel Server] SSE connection stored. Total connections: ${this.sseConnections.size}`);
492
- console.log(`[Tunnel Server] Active SSE IDs: ${Array.from(this.sseConnections.keys()).join(', ')}`);
493
- // Notify the tunnel client about the new SSE connection
494
- const sseConnectionMessage = {
495
- type: "sse_connection",
496
- id: sseId,
497
- path: req.url,
498
- method: req.method, // Include HTTP method for POST tool calls
499
- headers: req.headers,
500
- body: req.method !== "GET" && req.body ? req.body.toString("base64") : undefined
501
- };
502
- console.log(`[Tunnel Server] Sending sse_connection message to tunnel client`);
503
- try {
504
- tunnel.ws.send(JSON.stringify(sseConnectionMessage));
505
- console.log(`[Tunnel Server] sse_connection message sent successfully for ${sseId}`);
506
- }
507
- catch (sendError) {
508
- console.error(`[Tunnel Server] FAILED to send sse_connection message for ${sseId}:`, sendError);
509
- // Clean up the SSE connection if we can't notify the client
510
- this.sseConnections.delete(sseId);
511
- res.status(500).end();
512
- return;
513
- }
514
- // Handle client disconnect
515
- req.on('close', () => {
516
- console.log(`[Tunnel Server] SSE client disconnected: ${sseId}`);
517
- console.log(`[Tunnel Server] Connection was active for tunnel: ${tunnelId}`);
518
- clearInterval(keepaliveInterval);
519
- try {
520
- tunnel.ws.send(JSON.stringify({
521
- type: "sse_close",
522
- id: sseId
523
- }));
524
- console.log(`[Tunnel Server] Sent sse_close message for ${sseId}`);
525
- }
526
- catch (closeError) {
527
- console.error(`[Tunnel Server] Error sending sse_close message:`, closeError);
528
- }
529
- this.sseConnections.delete(sseId);
530
- console.log(`[Tunnel Server] SSE connection removed. Remaining: ${this.sseConnections.size}`);
531
- });
532
- // Add error handler for the response
533
- res.on('error', (error) => {
534
- console.error(`[Tunnel Server] SSE response error for ${sseId}:`, error);
535
- clearInterval(keepaliveInterval);
536
- this.sseConnections.delete(sseId);
537
- });
538
- console.log(`[Tunnel Server] ====== SSE CONNECTION SETUP COMPLETE ======`);
539
- }
540
- catch (error) {
541
- console.error(`[Tunnel Server] CRITICAL ERROR in handleSSERequest for ${sseId}:`, error);
442
+ // Set SSE headers with anti-buffering directives
443
+ res.writeHead(200, {
444
+ 'Content-Type': 'text/event-stream',
445
+ 'Cache-Control': 'no-cache',
446
+ 'Connection': 'keep-alive',
447
+ 'X-Accel-Buffering': 'no', // Disable nginx buffering
448
+ });
449
+ // Flush headers immediately to establish SSE connection
450
+ // Without this, headers may be buffered, delaying connection setup
451
+ res.flushHeaders();
452
+ // Send initial connection event
453
+ res.write('\n');
454
+ // Flush the initial write
455
+ flushSSE(res);
456
+ // Store the SSE connection
457
+ this.sseConnections.set(sseId, {
458
+ req,
459
+ res,
460
+ id: sseId,
461
+ tunnelId
462
+ });
463
+ // Notify the tunnel client about the new SSE connection
464
+ tunnel.ws.send(JSON.stringify({
465
+ type: "sse_connection",
466
+ id: sseId,
467
+ path: req.url,
468
+ method: req.method, // Include HTTP method for POST tool calls
469
+ headers: req.headers,
470
+ body: req.method !== "GET" && req.body ? req.body.toString("base64") : undefined
471
+ }));
472
+ // Handle client disconnect
473
+ req.on('close', () => {
474
+ tunnel.ws.send(JSON.stringify({
475
+ type: "sse_close",
476
+ id: sseId
477
+ }));
542
478
  this.sseConnections.delete(sseId);
543
- if (!res.headersSent) {
544
- res.status(500).send('SSE setup failed');
545
- }
546
- }
479
+ });
547
480
  }
548
481
  removeTunnel(ws) {
549
482
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dainprotocol/tunnel",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "private": false,
@@ -19,7 +19,7 @@
19
19
  "author": "Ryan",
20
20
  "license": "ISC",
21
21
  "dependencies": {
22
- "@dainprotocol/service-sdk": "^2.0.31",
22
+ "@dainprotocol/service-sdk": "^2.0.30",
23
23
  "@types/body-parser": "^1.19.5",
24
24
  "@types/cors": "^2.8.17",
25
25
  "@types/eventsource": "^3.0.0",