@dainprotocol/tunnel 1.1.35 → 2.0.0
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.d.ts +1 -6
- package/dist/client/index.js +120 -184
- package/dist/server/index.d.ts +6 -5
- package/dist/server/index.js +437 -372
- package/dist/server/start.js +4 -13
- package/package.json +8 -24
package/dist/client/index.d.ts
CHANGED
|
@@ -10,8 +10,7 @@ declare class DainTunnel extends EventEmitter {
|
|
|
10
10
|
private secret;
|
|
11
11
|
private webSocketClients;
|
|
12
12
|
private pendingWebSocketMessages;
|
|
13
|
-
private
|
|
14
|
-
private httpAgent;
|
|
13
|
+
private sseAbortControllers;
|
|
15
14
|
private heartbeatInterval;
|
|
16
15
|
private reconnectTimer;
|
|
17
16
|
private isStopping;
|
|
@@ -34,10 +33,6 @@ declare class DainTunnel extends EventEmitter {
|
|
|
34
33
|
private handleSSEClose;
|
|
35
34
|
private forwardRequest;
|
|
36
35
|
private attemptReconnect;
|
|
37
|
-
/**
|
|
38
|
-
* Reset reconnection attempts counter.
|
|
39
|
-
* Call this to allow reconnection after max_reconnect_attempts was reached.
|
|
40
|
-
*/
|
|
41
36
|
resetReconnection(): void;
|
|
42
37
|
stop(): Promise<void>;
|
|
43
38
|
}
|
package/dist/client/index.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.DainTunnel = void 0;
|
|
7
|
-
const ws_1 = __importDefault(require("ws"));
|
|
8
|
-
const http_1 = __importDefault(require("http"));
|
|
9
|
-
const events_1 = require("events");
|
|
10
|
-
const crypto_1 = require("crypto");
|
|
11
|
-
const auth_1 = require("@dainprotocol/service-sdk/service/auth");
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
import { createHmac } from "crypto";
|
|
4
|
+
import { parseAPIKey } from "@dainprotocol/service-sdk/service/auth";
|
|
12
5
|
const TIMEOUTS = {
|
|
13
6
|
HEARTBEAT: 25000,
|
|
14
7
|
REQUEST: 25000,
|
|
@@ -17,10 +10,10 @@ const TIMEOUTS = {
|
|
|
17
10
|
SHUTDOWN_GRACE: 500,
|
|
18
11
|
};
|
|
19
12
|
const RECONNECT = {
|
|
20
|
-
MAX_ATTEMPTS: 10,
|
|
21
|
-
BASE_DELAY: 1000,
|
|
22
|
-
MAX_DELAY: 30000,
|
|
23
|
-
JITTER: 0.3,
|
|
13
|
+
MAX_ATTEMPTS: 10,
|
|
14
|
+
BASE_DELAY: 1000,
|
|
15
|
+
MAX_DELAY: 30000,
|
|
16
|
+
JITTER: 0.3,
|
|
24
17
|
};
|
|
25
18
|
const LIMITS = {
|
|
26
19
|
MAX_PENDING_WS_MESSAGES_PER_CONNECTION: 256,
|
|
@@ -43,7 +36,7 @@ function rawDataToBuffer(data) {
|
|
|
43
36
|
return Buffer.from(data, "utf8");
|
|
44
37
|
return Buffer.from(data);
|
|
45
38
|
}
|
|
46
|
-
class DainTunnel extends
|
|
39
|
+
class DainTunnel extends EventEmitter {
|
|
47
40
|
constructor(serverUrl, apiKey) {
|
|
48
41
|
super();
|
|
49
42
|
this.serverUrl = serverUrl;
|
|
@@ -53,36 +46,29 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
53
46
|
this.reconnectAttempts = 0;
|
|
54
47
|
this.webSocketClients = new Map();
|
|
55
48
|
this.pendingWebSocketMessages = new Map();
|
|
56
|
-
this.
|
|
49
|
+
this.sseAbortControllers = new Map();
|
|
57
50
|
this.heartbeatInterval = null;
|
|
58
51
|
this.reconnectTimer = null;
|
|
59
52
|
this.isStopping = false;
|
|
60
|
-
const parsed =
|
|
53
|
+
const parsed = parseAPIKey(apiKey);
|
|
61
54
|
if (!parsed) {
|
|
62
55
|
throw new Error('Invalid API key format. Expected: sk_agent_{agentId}_{orgId}_{secret}');
|
|
63
56
|
}
|
|
64
57
|
this.apiKey = apiKey;
|
|
65
58
|
this.tunnelId = `${parsed.orgId}_${parsed.agentId}`;
|
|
66
59
|
this.secret = parsed.secret;
|
|
67
|
-
this.httpAgent = new http_1.default.Agent({
|
|
68
|
-
keepAlive: true,
|
|
69
|
-
keepAliveMsecs: 30000,
|
|
70
|
-
maxSockets: 100,
|
|
71
|
-
maxFreeSockets: 20,
|
|
72
|
-
});
|
|
73
60
|
}
|
|
74
61
|
signChallenge(challenge) {
|
|
75
|
-
return
|
|
62
|
+
return createHmac('sha256', this.secret).update(challenge).digest('hex');
|
|
76
63
|
}
|
|
77
64
|
safeSend(data) {
|
|
78
|
-
var _a;
|
|
79
65
|
try {
|
|
80
|
-
if (
|
|
66
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
81
67
|
this.ws.send(JSON.stringify(data));
|
|
82
68
|
return true;
|
|
83
69
|
}
|
|
84
70
|
}
|
|
85
|
-
catch
|
|
71
|
+
catch {
|
|
86
72
|
// Connection lost during send
|
|
87
73
|
}
|
|
88
74
|
return false;
|
|
@@ -90,12 +76,11 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
90
76
|
startHeartbeat() {
|
|
91
77
|
this.stopHeartbeat();
|
|
92
78
|
this.heartbeatInterval = setInterval(() => {
|
|
93
|
-
|
|
94
|
-
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === ws_1.default.OPEN) {
|
|
79
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
95
80
|
try {
|
|
96
81
|
this.ws.ping();
|
|
97
82
|
}
|
|
98
|
-
catch
|
|
83
|
+
catch {
|
|
99
84
|
this.emit("error", new Error("Heartbeat ping failed"));
|
|
100
85
|
}
|
|
101
86
|
}
|
|
@@ -132,7 +117,7 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
132
117
|
}
|
|
133
118
|
};
|
|
134
119
|
try {
|
|
135
|
-
this.ws = new
|
|
120
|
+
this.ws = new WebSocket(this.serverUrl);
|
|
136
121
|
this.ws.on("open", async () => {
|
|
137
122
|
this.reconnectAttempts = 0;
|
|
138
123
|
try {
|
|
@@ -177,13 +162,12 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
177
162
|
});
|
|
178
163
|
this.ws.on("error", (error) => this.emit("error", error));
|
|
179
164
|
connectionTimeoutId = setTimeout(() => {
|
|
180
|
-
|
|
181
|
-
if (!resolved && (!this.ws || this.ws.readyState !== ws_1.default.OPEN)) {
|
|
165
|
+
if (!resolved && (!this.ws || this.ws.readyState !== WebSocket.OPEN)) {
|
|
182
166
|
finish(undefined, new Error("Connection timeout"));
|
|
183
167
|
try {
|
|
184
|
-
|
|
168
|
+
this.ws?.terminate();
|
|
185
169
|
}
|
|
186
|
-
catch
|
|
170
|
+
catch { }
|
|
187
171
|
}
|
|
188
172
|
}, TIMEOUTS.CONNECTION);
|
|
189
173
|
}
|
|
@@ -193,19 +177,19 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
193
177
|
});
|
|
194
178
|
}
|
|
195
179
|
cleanupAllClients() {
|
|
196
|
-
for (const
|
|
180
|
+
for (const controller of this.sseAbortControllers.values()) {
|
|
197
181
|
try {
|
|
198
|
-
|
|
182
|
+
controller.abort();
|
|
199
183
|
}
|
|
200
|
-
catch
|
|
184
|
+
catch { }
|
|
201
185
|
}
|
|
202
|
-
this.
|
|
186
|
+
this.sseAbortControllers.clear();
|
|
203
187
|
for (const client of this.webSocketClients.values()) {
|
|
204
188
|
try {
|
|
205
|
-
if (client.readyState ===
|
|
189
|
+
if (client.readyState === WebSocket.OPEN)
|
|
206
190
|
client.close(1001);
|
|
207
191
|
}
|
|
208
|
-
catch
|
|
192
|
+
catch { }
|
|
209
193
|
}
|
|
210
194
|
this.webSocketClients.clear();
|
|
211
195
|
this.pendingWebSocketMessages.clear();
|
|
@@ -219,11 +203,10 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
219
203
|
let resolved = false;
|
|
220
204
|
let timeoutId = null;
|
|
221
205
|
const finish = (challenge, error) => {
|
|
222
|
-
var _a;
|
|
223
206
|
if (resolved)
|
|
224
207
|
return;
|
|
225
208
|
resolved = true;
|
|
226
|
-
|
|
209
|
+
this.ws?.removeListener("message", challengeHandler);
|
|
227
210
|
if (timeoutId) {
|
|
228
211
|
clearTimeout(timeoutId);
|
|
229
212
|
timeoutId = null;
|
|
@@ -242,7 +225,7 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
242
225
|
finish(data.challenge);
|
|
243
226
|
}
|
|
244
227
|
}
|
|
245
|
-
catch
|
|
228
|
+
catch { }
|
|
246
229
|
};
|
|
247
230
|
this.ws.on("message", challengeHandler);
|
|
248
231
|
this.ws.send(JSON.stringify({ type: "challenge_request" }));
|
|
@@ -301,15 +284,14 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
301
284
|
this.safeSend({ type: 'websocket', id: message.id, event, data });
|
|
302
285
|
};
|
|
303
286
|
try {
|
|
304
|
-
// Preserve original host for JWT audience validation
|
|
305
287
|
const headers = { ...message.headers };
|
|
306
288
|
const originalHost = message.headers.host;
|
|
307
289
|
if (originalHost && !headers['x-forwarded-host']) {
|
|
308
290
|
headers['x-forwarded-host'] = originalHost;
|
|
309
291
|
headers['x-forwarded-proto'] = 'https';
|
|
310
292
|
}
|
|
311
|
-
delete headers.host;
|
|
312
|
-
const client = new
|
|
293
|
+
delete headers.host;
|
|
294
|
+
const client = new WebSocket(`ws://localhost:${this.port}${message.path}`, {
|
|
313
295
|
headers
|
|
314
296
|
});
|
|
315
297
|
this.pendingWebSocketMessages.set(message.id, []);
|
|
@@ -342,11 +324,11 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
342
324
|
case 'message':
|
|
343
325
|
if (!message.data)
|
|
344
326
|
return;
|
|
345
|
-
if (client.readyState ===
|
|
327
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
346
328
|
client.send(Buffer.from(message.data, 'base64'));
|
|
347
329
|
return;
|
|
348
330
|
}
|
|
349
|
-
if (client.readyState !==
|
|
331
|
+
if (client.readyState !== WebSocket.CONNECTING)
|
|
350
332
|
return;
|
|
351
333
|
const pending = this.pendingWebSocketMessages.get(message.id);
|
|
352
334
|
if (!pending)
|
|
@@ -359,13 +341,13 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
359
341
|
pending.push(Buffer.from(message.data, 'base64'));
|
|
360
342
|
break;
|
|
361
343
|
case 'close':
|
|
362
|
-
if (client.readyState ===
|
|
344
|
+
if (client.readyState === WebSocket.OPEN || client.readyState === WebSocket.CONNECTING) {
|
|
363
345
|
client.close();
|
|
364
346
|
}
|
|
365
347
|
this.cleanupWebSocketConnection(message.id);
|
|
366
348
|
break;
|
|
367
349
|
case 'error':
|
|
368
|
-
if (client.readyState ===
|
|
350
|
+
if (client.readyState === WebSocket.OPEN || client.readyState === WebSocket.CONNECTING) {
|
|
369
351
|
client.close(1011, message.data);
|
|
370
352
|
}
|
|
371
353
|
this.cleanupWebSocketConnection(message.id);
|
|
@@ -378,7 +360,7 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
378
360
|
if (!client || !pending || pending.length === 0)
|
|
379
361
|
return;
|
|
380
362
|
try {
|
|
381
|
-
while (pending.length > 0 && client.readyState ===
|
|
363
|
+
while (pending.length > 0 && client.readyState === WebSocket.OPEN) {
|
|
382
364
|
client.send(pending.shift());
|
|
383
365
|
}
|
|
384
366
|
}
|
|
@@ -397,7 +379,6 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
397
379
|
};
|
|
398
380
|
try {
|
|
399
381
|
const headers = { ...message.headers };
|
|
400
|
-
// Preserve original host for JWT audience validation
|
|
401
382
|
const originalHost = message.headers.host;
|
|
402
383
|
if (originalHost && !headers['x-forwarded-host']) {
|
|
403
384
|
headers['x-forwarded-host'] = originalHost;
|
|
@@ -405,34 +386,35 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
405
386
|
}
|
|
406
387
|
delete headers.host;
|
|
407
388
|
headers['accept-encoding'] = 'identity';
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
389
|
+
const abortController = new AbortController();
|
|
390
|
+
this.sseAbortControllers.set(message.id, abortController);
|
|
391
|
+
const url = `http://localhost:${this.port}${message.path}`;
|
|
392
|
+
const fetchOptions = {
|
|
412
393
|
method: message.method || 'GET',
|
|
413
394
|
headers,
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
res.
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
});
|
|
426
|
-
res.on('close', cleanup);
|
|
395
|
+
signal: abortController.signal,
|
|
396
|
+
};
|
|
397
|
+
if (message.body && message.method !== 'GET') {
|
|
398
|
+
fetchOptions.body = Buffer.from(message.body, 'base64');
|
|
399
|
+
}
|
|
400
|
+
fetch(url, fetchOptions)
|
|
401
|
+
.then(async (res) => {
|
|
402
|
+
if (res.status !== 200) {
|
|
403
|
+
const errorBody = await res.text();
|
|
404
|
+
sendSseEvent('error', `Status ${res.status}: ${errorBody.substring(0, 200)}`);
|
|
405
|
+
this.sseAbortControllers.delete(message.id);
|
|
427
406
|
return;
|
|
428
407
|
}
|
|
429
|
-
const socket = res.socket || res.connection;
|
|
430
|
-
if (socket === null || socket === void 0 ? void 0 : socket.setNoDelay)
|
|
431
|
-
socket.setNoDelay(true);
|
|
432
408
|
sendSseEvent('connected');
|
|
409
|
+
if (!res.body) {
|
|
410
|
+
sendSseEvent('close');
|
|
411
|
+
this.sseAbortControllers.delete(message.id);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const reader = res.body.getReader();
|
|
415
|
+
const decoder = new TextDecoder();
|
|
433
416
|
let buffer = '';
|
|
434
417
|
const processBuffer = () => {
|
|
435
|
-
// Normalize line endings once
|
|
436
418
|
if (buffer.indexOf('\r') >= 0) {
|
|
437
419
|
buffer = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
438
420
|
}
|
|
@@ -440,7 +422,6 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
440
422
|
while ((idx = buffer.indexOf('\n\n')) >= 0) {
|
|
441
423
|
const msgData = buffer.slice(0, idx);
|
|
442
424
|
buffer = buffer.slice(idx + 2);
|
|
443
|
-
// Skip empty messages and comments
|
|
444
425
|
if (!msgData || msgData[0] === ':')
|
|
445
426
|
continue;
|
|
446
427
|
let event = 'message';
|
|
@@ -459,40 +440,36 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
459
440
|
sendSseEvent(event, data);
|
|
460
441
|
}
|
|
461
442
|
};
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
443
|
+
try {
|
|
444
|
+
while (true) {
|
|
445
|
+
const { done, value } = await reader.read();
|
|
446
|
+
if (done)
|
|
447
|
+
break;
|
|
448
|
+
buffer += decoder.decode(value, { stream: true });
|
|
449
|
+
processBuffer();
|
|
450
|
+
}
|
|
451
|
+
// Process remaining buffer
|
|
467
452
|
if (buffer.trim()) {
|
|
468
453
|
buffer += '\n\n';
|
|
469
454
|
processBuffer();
|
|
470
455
|
}
|
|
471
456
|
sendSseEvent('close');
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
if (err.name !== 'AbortError') {
|
|
460
|
+
sendSseEvent('error', err.message);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
finally {
|
|
464
|
+
this.sseAbortControllers.delete(message.id);
|
|
465
|
+
}
|
|
466
|
+
})
|
|
467
|
+
.catch((error) => {
|
|
468
|
+
if (error.name !== 'AbortError') {
|
|
476
469
|
sendSseEvent('error', error.message);
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
});
|
|
480
|
-
req.on('socket', (socket) => {
|
|
481
|
-
socket.setNoDelay(true);
|
|
482
|
-
socket.setTimeout(0);
|
|
483
|
-
});
|
|
484
|
-
req.on('error', (error) => {
|
|
485
|
-
sendSseEvent('error', error.message);
|
|
486
|
-
this.sseClients.delete(message.id);
|
|
487
|
-
});
|
|
488
|
-
req.on('close', () => {
|
|
489
|
-
this.sseClients.delete(message.id);
|
|
470
|
+
}
|
|
471
|
+
this.sseAbortControllers.delete(message.id);
|
|
490
472
|
});
|
|
491
|
-
this.sseClients.set(message.id, req);
|
|
492
|
-
if (message.body && message.method !== 'GET') {
|
|
493
|
-
req.write(Buffer.from(message.body, 'base64'));
|
|
494
|
-
}
|
|
495
|
-
req.end();
|
|
496
473
|
this.emit("sse_connection", { id: message.id, path: message.path });
|
|
497
474
|
}
|
|
498
475
|
catch (error) {
|
|
@@ -500,79 +477,44 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
500
477
|
}
|
|
501
478
|
}
|
|
502
479
|
handleSSEClose(message) {
|
|
503
|
-
const
|
|
504
|
-
if (
|
|
505
|
-
|
|
506
|
-
this.
|
|
480
|
+
const controller = this.sseAbortControllers.get(message.id);
|
|
481
|
+
if (controller) {
|
|
482
|
+
controller.abort();
|
|
483
|
+
this.sseAbortControllers.delete(message.id);
|
|
507
484
|
}
|
|
508
485
|
}
|
|
509
|
-
forwardRequest(request) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
if (originalHost && !headers['x-forwarded-host']) {
|
|
532
|
-
headers['x-forwarded-host'] = originalHost;
|
|
533
|
-
headers['x-forwarded-proto'] = 'https';
|
|
534
|
-
}
|
|
535
|
-
delete headers.host;
|
|
536
|
-
const options = {
|
|
537
|
-
hostname: 'localhost',
|
|
538
|
-
port: this.port,
|
|
539
|
-
path: request.path,
|
|
540
|
-
method: request.method,
|
|
541
|
-
headers,
|
|
542
|
-
agent: this.httpAgent,
|
|
543
|
-
timeout: TIMEOUTS.REQUEST,
|
|
544
|
-
};
|
|
545
|
-
const req = http_1.default.request(options, (res) => {
|
|
546
|
-
const chunks = [];
|
|
547
|
-
res.on('data', (chunk) => chunks.push(chunk));
|
|
548
|
-
res.on('end', () => {
|
|
549
|
-
const headers = { ...res.headers };
|
|
550
|
-
delete headers['transfer-encoding'];
|
|
551
|
-
delete headers['content-length'];
|
|
552
|
-
finish({
|
|
553
|
-
type: 'response',
|
|
554
|
-
requestId: request.id,
|
|
555
|
-
status: res.statusCode,
|
|
556
|
-
headers,
|
|
557
|
-
body: Buffer.concat(chunks).toString('base64'),
|
|
558
|
-
});
|
|
559
|
-
});
|
|
560
|
-
res.on('error', (err) => finish(undefined, err));
|
|
561
|
-
});
|
|
562
|
-
req.on('timeout', () => {
|
|
563
|
-
req.destroy();
|
|
564
|
-
finish(undefined, new Error(`Request timeout after ${TIMEOUTS.REQUEST}ms`));
|
|
565
|
-
});
|
|
566
|
-
req.on('error', (err) => finish(undefined, err));
|
|
567
|
-
timeoutId = setTimeout(() => {
|
|
568
|
-
req.destroy();
|
|
569
|
-
finish(undefined, new Error(`Request timeout after ${TIMEOUTS.REQUEST}ms`));
|
|
570
|
-
}, TIMEOUTS.REQUEST);
|
|
571
|
-
if (request.body && request.method !== 'GET') {
|
|
572
|
-
req.write(Buffer.from(request.body, 'base64'));
|
|
573
|
-
}
|
|
574
|
-
req.end();
|
|
486
|
+
async forwardRequest(request) {
|
|
487
|
+
const headers = { ...request.headers };
|
|
488
|
+
const originalHost = request.headers.host;
|
|
489
|
+
if (originalHost && !headers['x-forwarded-host']) {
|
|
490
|
+
headers['x-forwarded-host'] = originalHost;
|
|
491
|
+
headers['x-forwarded-proto'] = 'https';
|
|
492
|
+
}
|
|
493
|
+
delete headers.host;
|
|
494
|
+
const url = `http://localhost:${this.port}${request.path}`;
|
|
495
|
+
const fetchOptions = {
|
|
496
|
+
method: request.method,
|
|
497
|
+
headers,
|
|
498
|
+
signal: AbortSignal.timeout(TIMEOUTS.REQUEST),
|
|
499
|
+
};
|
|
500
|
+
if (request.body && request.method !== 'GET') {
|
|
501
|
+
fetchOptions.body = Buffer.from(request.body, 'base64');
|
|
502
|
+
}
|
|
503
|
+
const res = await fetch(url, fetchOptions);
|
|
504
|
+
const responseBody = await res.arrayBuffer();
|
|
505
|
+
const responseHeaders = {};
|
|
506
|
+
res.headers.forEach((value, key) => {
|
|
507
|
+
responseHeaders[key] = value;
|
|
575
508
|
});
|
|
509
|
+
delete responseHeaders['transfer-encoding'];
|
|
510
|
+
delete responseHeaders['content-length'];
|
|
511
|
+
return {
|
|
512
|
+
type: 'response',
|
|
513
|
+
requestId: request.id,
|
|
514
|
+
status: res.status,
|
|
515
|
+
headers: responseHeaders,
|
|
516
|
+
body: Buffer.from(responseBody).toString('base64'),
|
|
517
|
+
};
|
|
576
518
|
}
|
|
577
519
|
attemptReconnect() {
|
|
578
520
|
if (this.isStopping)
|
|
@@ -582,7 +524,6 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
582
524
|
return;
|
|
583
525
|
}
|
|
584
526
|
this.reconnectAttempts++;
|
|
585
|
-
// Exponential backoff with jitter
|
|
586
527
|
const baseDelay = Math.min(RECONNECT.BASE_DELAY * Math.pow(2, this.reconnectAttempts - 1), RECONNECT.MAX_DELAY);
|
|
587
528
|
const jitter = baseDelay * RECONNECT.JITTER * (Math.random() - 0.5);
|
|
588
529
|
const delay = Math.round(baseDelay + jitter);
|
|
@@ -592,16 +533,12 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
592
533
|
await this.connect();
|
|
593
534
|
this.emit("reconnected");
|
|
594
535
|
}
|
|
595
|
-
catch
|
|
536
|
+
catch {
|
|
596
537
|
if (!this.isStopping)
|
|
597
538
|
this.attemptReconnect();
|
|
598
539
|
}
|
|
599
540
|
}, delay);
|
|
600
541
|
}
|
|
601
|
-
/**
|
|
602
|
-
* Reset reconnection attempts counter.
|
|
603
|
-
* Call this to allow reconnection after max_reconnect_attempts was reached.
|
|
604
|
-
*/
|
|
605
542
|
resetReconnection() {
|
|
606
543
|
this.reconnectAttempts = 0;
|
|
607
544
|
}
|
|
@@ -615,18 +552,17 @@ class DainTunnel extends events_1.EventEmitter {
|
|
|
615
552
|
}
|
|
616
553
|
if (this.ws) {
|
|
617
554
|
try {
|
|
618
|
-
if (this.ws.readyState ===
|
|
555
|
+
if (this.ws.readyState === WebSocket.OPEN)
|
|
619
556
|
this.ws.close();
|
|
620
557
|
}
|
|
621
|
-
catch
|
|
558
|
+
catch { }
|
|
622
559
|
this.ws = null;
|
|
623
560
|
}
|
|
624
561
|
this.tunnelUrl = null;
|
|
625
|
-
this.httpAgent.destroy();
|
|
626
562
|
await new Promise(resolve => setTimeout(resolve, TIMEOUTS.SHUTDOWN_GRACE));
|
|
627
563
|
}
|
|
628
564
|
}
|
|
629
|
-
|
|
630
|
-
|
|
565
|
+
export { DainTunnel };
|
|
566
|
+
export default {
|
|
631
567
|
createTunnel: (serverUrl, apiKey) => new DainTunnel(serverUrl, apiKey),
|
|
632
568
|
};
|
package/dist/server/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ declare class DainTunnelServer {
|
|
|
3
3
|
private port;
|
|
4
4
|
private app;
|
|
5
5
|
private server;
|
|
6
|
-
private
|
|
6
|
+
private allowedCorsOrigins;
|
|
7
7
|
private tunnels;
|
|
8
8
|
private pendingRequests;
|
|
9
9
|
private challenges;
|
|
@@ -15,10 +15,11 @@ declare class DainTunnelServer {
|
|
|
15
15
|
private buildForwardedHeaders;
|
|
16
16
|
private buildTunnelUrl;
|
|
17
17
|
constructor(hostname: string, port: number);
|
|
18
|
-
private
|
|
19
|
-
private
|
|
20
|
-
private
|
|
21
|
-
private
|
|
18
|
+
private setupRoutes;
|
|
19
|
+
private handleWsOpen;
|
|
20
|
+
private handleWsMessage;
|
|
21
|
+
private handleWsClose;
|
|
22
|
+
private handleProxiedWebSocketClientMessage;
|
|
22
23
|
private handleChallengeRequest;
|
|
23
24
|
private handleStartMessage;
|
|
24
25
|
private handleResponseMessage;
|