@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.
- package/dist/client/index.js +21 -8
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +50 -49
- package/package.json +3 -3
package/dist/client/index.js
CHANGED
|
@@ -234,26 +234,39 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
234
234
|
handleSSEConnection(message) {
|
|
235
235
|
var _a;
|
|
236
236
|
try {
|
|
237
|
-
const
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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: '
|
|
295
|
+
this.ws.send(JSON.stringify({ type: 'sse', id: message.id, event: 'close', data: '' }));
|
|
283
296
|
}
|
|
284
297
|
});
|
|
285
298
|
});
|
package/dist/server/index.d.ts
CHANGED
package/dist/server/index.js
CHANGED
|
@@ -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
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
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:
|
|
352
|
+
res.write(`event: error\ndata: ${data.data}\n\n`);
|
|
343
353
|
flushSSE(res);
|
|
344
354
|
res.end();
|
|
345
355
|
}
|
|
346
|
-
catch (
|
|
356
|
+
catch (_b) { }
|
|
347
357
|
}
|
|
348
|
-
|
|
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
|
-
|
|
355
|
-
|
|
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
|
-
|
|
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 (
|
|
366
|
-
|
|
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
|
|
469
|
+
// Optimize TCP for streaming
|
|
457
470
|
const socket = req.socket || req.connection;
|
|
458
|
-
if (socket
|
|
459
|
-
socket.setNoDelay(true);
|
|
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',
|
|
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, {
|
|
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
|
-
|
|
486
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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.
|
|
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.
|
|
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
|
+
}
|