@dainprotocol/tunnel 1.1.18 → 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 -27
- 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
|
@@ -233,33 +233,41 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
233
233
|
}
|
|
234
234
|
handleSSEConnection(message) {
|
|
235
235
|
var _a;
|
|
236
|
-
const startTime = Date.now();
|
|
237
|
-
console.log(`[SSE E2E] Client: Establishing SSE connection ${message.id} to localhost:${this.port}${message.path}`);
|
|
238
236
|
try {
|
|
239
|
-
const
|
|
237
|
+
const req = http_1.default.request({
|
|
240
238
|
hostname: 'localhost',
|
|
241
239
|
port: this.port,
|
|
242
240
|
path: message.path,
|
|
243
241
|
method: message.method || 'GET',
|
|
244
242
|
headers: message.headers,
|
|
245
243
|
agent: this.httpAgent,
|
|
246
|
-
}
|
|
247
|
-
const req = http_1.default.request(options, (res) => {
|
|
244
|
+
}, (res) => {
|
|
248
245
|
var _a;
|
|
246
|
+
// Non-200 response - forward error to tunnel server
|
|
249
247
|
if (res.statusCode !== 200) {
|
|
250
|
-
|
|
251
|
-
res.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
+
});
|
|
255
259
|
return;
|
|
256
260
|
}
|
|
257
|
-
|
|
261
|
+
// Optimize TCP for streaming
|
|
258
262
|
const socket = res.socket || res.connection;
|
|
259
263
|
if (socket === null || socket === void 0 ? void 0 : socket.setNoDelay)
|
|
260
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
|
|
261
270
|
let buffer = '';
|
|
262
|
-
let eventCount = 0;
|
|
263
271
|
res.on('data', (chunk) => {
|
|
264
272
|
var _a;
|
|
265
273
|
buffer += chunk.toString();
|
|
@@ -276,28 +284,20 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
276
284
|
}
|
|
277
285
|
if (data.endsWith('\n'))
|
|
278
286
|
data = data.slice(0, -1);
|
|
279
|
-
eventCount++;
|
|
280
|
-
console.log(`[SSE E2E] Client: Received event #${eventCount} from service for ${message.id}: ${event} (${Date.now() - startTime}ms)`);
|
|
281
287
|
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === ws_1.default.OPEN) {
|
|
282
288
|
this.ws.send(JSON.stringify({ type: 'sse', id: message.id, event, data }));
|
|
283
|
-
console.log(`[SSE E2E] Client: Forwarded event to tunnel server for ${message.id}`);
|
|
284
|
-
}
|
|
285
|
-
else {
|
|
286
|
-
console.log(`[SSE E2E] Client: WebSocket closed, cannot forward event for ${message.id}`);
|
|
287
289
|
}
|
|
288
290
|
}
|
|
289
291
|
});
|
|
290
292
|
res.on('end', () => {
|
|
291
293
|
var _a;
|
|
292
|
-
console.log(`[SSE E2E] Client: Service closed connection for ${message.id} after ${eventCount} events (${Date.now() - startTime}ms)`);
|
|
293
294
|
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === ws_1.default.OPEN) {
|
|
294
|
-
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: '' }));
|
|
295
296
|
}
|
|
296
297
|
});
|
|
297
298
|
});
|
|
298
299
|
req.on('error', (error) => {
|
|
299
300
|
var _a;
|
|
300
|
-
console.log(`[SSE E2E] Client: Request error for ${message.id}: ${error.message}`);
|
|
301
301
|
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === ws_1.default.OPEN) {
|
|
302
302
|
this.ws.send(JSON.stringify({ type: 'sse', id: message.id, event: 'error', data: error.message }));
|
|
303
303
|
}
|
|
@@ -309,23 +309,17 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
309
309
|
this.emit("sse_connection", { id: message.id, path: message.path });
|
|
310
310
|
}
|
|
311
311
|
catch (error) {
|
|
312
|
-
console.log(`[SSE E2E] Client: Exception for ${message.id}: ${error.message}`);
|
|
313
312
|
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === ws_1.default.OPEN) {
|
|
314
313
|
this.ws.send(JSON.stringify({ type: 'sse', id: message.id, event: 'error', data: error.message }));
|
|
315
314
|
}
|
|
316
315
|
}
|
|
317
316
|
}
|
|
318
317
|
handleSSEClose(message) {
|
|
319
|
-
console.log(`[SSE E2E] Client: Received close command from server for ${message.id}`);
|
|
320
318
|
const client = this.sseClients.get(message.id);
|
|
321
319
|
if (client) {
|
|
322
|
-
console.log(`[SSE E2E] Client: Destroying local service connection for ${message.id}`);
|
|
323
320
|
client.destroy();
|
|
324
321
|
this.sseClients.delete(message.id);
|
|
325
322
|
}
|
|
326
|
-
else {
|
|
327
|
-
console.log(`[SSE E2E] Client: No active connection found for ${message.id} (already closed?)`);
|
|
328
|
-
}
|
|
329
323
|
}
|
|
330
324
|
forwardRequest(request) {
|
|
331
325
|
return new Promise((resolve, reject) => {
|
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
|
+
}
|