@dainprotocol/tunnel 1.1.17 → 1.1.21

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.
@@ -234,26 +234,39 @@ class DainTunnel extends events_1.EventEmitter {
234
234
  handleSSEConnection(message) {
235
235
  var _a;
236
236
  try {
237
- const options = {
237
+ const req = http_1.default.request({
238
238
  hostname: 'localhost',
239
239
  port: this.port,
240
240
  path: message.path,
241
241
  method: message.method || 'GET',
242
242
  headers: message.headers,
243
243
  agent: this.httpAgent,
244
- };
245
- const req = http_1.default.request(options, (res) => {
244
+ }, (res) => {
246
245
  var _a;
246
+ // Non-200 response - forward error to tunnel server
247
247
  if (res.statusCode !== 200) {
248
- res.resume();
249
- if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === ws_1.default.OPEN) {
250
- this.ws.send(JSON.stringify({ type: 'sse', id: message.id, event: 'error', data: `Status ${res.statusCode}` }));
251
- }
248
+ let errorBody = '';
249
+ res.on('data', (chunk) => { errorBody += chunk.toString(); });
250
+ res.on('end', () => {
251
+ var _a;
252
+ if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === ws_1.default.OPEN) {
253
+ this.ws.send(JSON.stringify({
254
+ type: 'sse', id: message.id, event: 'error',
255
+ data: `Status ${res.statusCode}: ${errorBody.substring(0, 200)}`
256
+ }));
257
+ }
258
+ });
252
259
  return;
253
260
  }
261
+ // Optimize TCP for streaming
254
262
  const socket = res.socket || res.connection;
255
263
  if (socket === null || socket === void 0 ? void 0 : socket.setNoDelay)
256
264
  socket.setNoDelay(true);
265
+ // Notify tunnel server that local service accepted
266
+ if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === ws_1.default.OPEN) {
267
+ this.ws.send(JSON.stringify({ type: 'sse', id: message.id, event: 'connected', data: '' }));
268
+ }
269
+ // Stream SSE events to tunnel server
257
270
  let buffer = '';
258
271
  res.on('data', (chunk) => {
259
272
  var _a;
@@ -279,7 +292,7 @@ class DainTunnel extends events_1.EventEmitter {
279
292
  res.on('end', () => {
280
293
  var _a;
281
294
  if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === ws_1.default.OPEN) {
282
- this.ws.send(JSON.stringify({ type: 'sse', id: message.id, event: 'close', data: 'Connection closed' }));
295
+ this.ws.send(JSON.stringify({ type: 'sse', id: message.id, event: 'close', data: '' }));
283
296
  }
284
297
  });
285
298
  });
@@ -20,6 +20,7 @@ declare class DainTunnelServer {
20
20
  private handleStartMessage;
21
21
  private handleResponseMessage;
22
22
  private handleSSEMessage;
23
+ private cleanupSSEConnection;
23
24
  private handleWebSocketMessage;
24
25
  private handleRequest;
25
26
  private handleSSERequest;
@@ -324,48 +324,61 @@ class DainTunnelServer {
324
324
  }
325
325
  handleSSEMessage(data) {
326
326
  const connection = this.sseConnections.get(data.id);
327
- if (!connection) {
328
- console.log(`[SSE Debug] handleSSEMessage: No connection found for ${data.id}, event=${data.event}`);
327
+ if (!connection)
329
328
  return;
330
- }
331
329
  const { res, tunnelId } = connection;
332
- const clientDisconnected = connection.clientDisconnected;
333
- const isCloseEvent = data.event === 'close';
334
- console.log(`[SSE Debug] handleSSEMessage: id=${data.id}, event=${data.event}, clientDisconnected=${clientDisconnected}, isClose=${isCloseEvent}`);
335
- if (isCloseEvent) {
330
+ const conn = connection;
331
+ // Skip 'connected' event - we already wrote headers
332
+ if (data.event === 'connected')
333
+ return;
334
+ // Handle close event
335
+ if (data.event === 'close') {
336
336
  const tunnel = this.tunnels.get(tunnelId);
337
- if (tunnel) {
337
+ if (tunnel)
338
338
  tunnel.ws.send(JSON.stringify({ type: "sse_close", id: data.id }));
339
+ if (!conn.clientDisconnected && res.writable) {
340
+ try {
341
+ res.end();
342
+ }
343
+ catch (_a) { }
339
344
  }
340
- if (!clientDisconnected && res.writable) {
345
+ this.cleanupSSEConnection(data.id, tunnelId);
346
+ return;
347
+ }
348
+ // Handle error - send as SSE error event (headers already written)
349
+ if (data.event === 'error') {
350
+ if (!conn.clientDisconnected && res.writable) {
341
351
  try {
342
- res.write(`event: ${data.event}\ndata: ${data.data}\n\n`);
352
+ res.write(`event: error\ndata: ${data.data}\n\n`);
343
353
  flushSSE(res);
344
354
  res.end();
345
355
  }
346
- catch (error) { /* ignore */ }
356
+ catch (_b) { }
347
357
  }
348
- // Decrement request counter for backpressure tracking
349
- const currentCount = this.tunnelRequestCount.get(tunnelId) || 1;
350
- this.tunnelRequestCount.set(tunnelId, Math.max(0, currentCount - 1));
351
- this.sseConnections.delete(data.id);
358
+ this.cleanupSSEConnection(data.id, tunnelId);
352
359
  return;
353
360
  }
354
- if (clientDisconnected || !res.writable) {
355
- connection.clientDisconnected = true;
361
+ // Skip if client disconnected
362
+ if (conn.clientDisconnected || !res.writable) {
363
+ conn.clientDisconnected = true;
356
364
  return;
357
365
  }
366
+ // Forward SSE event
358
367
  try {
359
368
  if (data.event)
360
369
  res.write(`event: ${data.event}\n`);
361
- const escapedData = data.data.replace(/\n/g, '\ndata: ');
362
- res.write(`data: ${escapedData}\n\n`);
370
+ res.write(`data: ${data.data.replace(/\n/g, '\ndata: ')}\n\n`);
363
371
  flushSSE(res);
364
372
  }
365
- catch (error) {
366
- connection.clientDisconnected = true;
373
+ catch (_c) {
374
+ conn.clientDisconnected = true;
367
375
  }
368
376
  }
377
+ cleanupSSEConnection(id, tunnelId) {
378
+ const currentCount = this.tunnelRequestCount.get(tunnelId) || 1;
379
+ this.tunnelRequestCount.set(tunnelId, Math.max(0, currentCount - 1));
380
+ this.sseConnections.delete(id);
381
+ }
369
382
  handleWebSocketMessage(data) {
370
383
  const connection = this.wsConnections.get(data.id);
371
384
  if (!connection)
@@ -453,27 +466,24 @@ class DainTunnelServer {
453
466
  }
454
467
  handleSSERequest(req, res, tunnelId, tunnel) {
455
468
  const sseId = fastId();
456
- // Optimize TCP socket for low-latency streaming
469
+ // Optimize TCP for streaming
457
470
  const socket = req.socket || req.connection;
458
- if (socket && socket.setNoDelay) {
459
- socket.setNoDelay(true); // Disable Nagle's algorithm for immediate transmission
460
- }
461
- // Set SSE headers with anti-buffering directives
462
- // Note: Do NOT set 'Connection: keep-alive' - it's forbidden in HTTP/2 and will cause immediate disconnect
471
+ if (socket === null || socket === void 0 ? void 0 : socket.setNoDelay)
472
+ socket.setNoDelay(true);
473
+ // Write SSE headers immediately to keep browser connection alive
463
474
  res.writeHead(200, {
464
475
  'Content-Type': 'text/event-stream',
465
476
  'Cache-Control': 'no-cache',
466
- 'X-Accel-Buffering': 'no', // Disable nginx buffering
477
+ 'X-Accel-Buffering': 'no',
467
478
  });
468
- // Flush headers immediately to establish SSE connection
469
- // Without this, headers may be buffered, delaying connection setup
470
479
  res.flushHeaders();
471
- // Send SSE comment to keep connection alive and verify it's working
472
- // Comments start with : and are ignored by EventSource but flush the buffer
473
480
  res.write(': connected\n\n');
474
- // Flush the initial write
475
481
  flushSSE(res);
476
- this.sseConnections.set(sseId, { req, res, id: sseId, tunnelId });
482
+ this.sseConnections.set(sseId, {
483
+ req, res, id: sseId, tunnelId,
484
+ clientDisconnected: false
485
+ });
486
+ // Forward to tunnel client
477
487
  tunnel.ws.send(JSON.stringify({
478
488
  type: "sse_connection",
479
489
  id: sseId,
@@ -482,31 +492,22 @@ class DainTunnelServer {
482
492
  headers: req.headers,
483
493
  body: req.method !== "GET" && req.body ? req.body.toString("base64") : undefined
484
494
  }));
485
- req.on('close', () => {
486
- console.log(`[SSE Debug] req.close fired for ${sseId} - client disconnected`);
495
+ // Track response close (not request close) - for POST SSE, req closes immediately
496
+ res.on('close', () => {
487
497
  const connection = this.sseConnections.get(sseId);
488
498
  if (connection) {
489
499
  connection.clientDisconnected = true;
490
- // Clean up after a short delay to allow any pending events to be handled
491
500
  setTimeout(() => {
492
501
  if (this.sseConnections.has(sseId)) {
493
- console.log(`[SSE Debug] Cleaning up SSE connection ${sseId} after client disconnect`);
494
- // Decrement request counter for backpressure tracking
495
- const currentCount = this.tunnelRequestCount.get(tunnelId) || 1;
496
- this.tunnelRequestCount.set(tunnelId, Math.max(0, currentCount - 1));
497
- this.sseConnections.delete(sseId);
498
- // Notify tunnel client to close the connection
499
- const tunnel = this.tunnels.get(tunnelId);
500
- if (tunnel && tunnel.ws.readyState === ws_1.default.OPEN) {
501
- tunnel.ws.send(JSON.stringify({ type: "sse_close", id: sseId }));
502
+ this.cleanupSSEConnection(sseId, tunnelId);
503
+ const t = this.tunnels.get(tunnelId);
504
+ if ((t === null || t === void 0 ? void 0 : t.ws.readyState) === ws_1.default.OPEN) {
505
+ t.ws.send(JSON.stringify({ type: "sse_close", id: sseId }));
502
506
  }
503
507
  }
504
508
  }, 100);
505
509
  }
506
510
  });
507
- req.on('error', (err) => {
508
- console.log(`[SSE Debug] req.error fired for ${sseId}:`, err.message);
509
- });
510
511
  }
511
512
  removeTunnel(ws) {
512
513
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dainprotocol/tunnel",
3
- "version": "1.1.17",
3
+ "version": "1.1.21",
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.56",
22
+ "@dainprotocol/service-sdk": "2.0.57",
23
23
  "@types/body-parser": "^1.19.5",
24
24
  "@types/cors": "^2.8.17",
25
25
  "@types/eventsource": "^3.0.0",
@@ -77,4 +77,4 @@
77
77
  ]
78
78
  }
79
79
  }
80
- }
80
+ }