js-tcp-tunnel 1.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/src/lib.js ADDED
@@ -0,0 +1,1539 @@
1
+ import { createHash } from 'node:crypto'
2
+ import net from 'node:net'
3
+ import { Readable, Writable } from 'node:stream'
4
+
5
+ /**
6
+ * @import {WebSocketServer} from 'ws'
7
+ * @import Router from 'koa-router'
8
+ */
9
+
10
+ const DEBUG_TUNNEL_TCP = false
11
+ const TUNNEL_TCP_WITH_CRYPTO = true
12
+ const TUNNEL_TCP_TIME_BUFFERED = false
13
+ // const TUNNEL_TCP_QUEUE_SIZE = 15
14
+ // const TUNNEL_TCP_ACK_SIZE = 10
15
+
16
+ export const md5 = (/**@type{string}*/s) => createHash("md5").update(s).digest('hex')
17
+ export const sleep = (/** @type {number} */ timeout) => new Promise((resolve) => setTimeout(resolve, timeout))
18
+ export const sha256 = (/**@type{string}*/s) => createHash("sha256").update(s).digest('hex')
19
+ export const sha512 = (/**@type{string}*/s) => createHash("sha512").update(s).digest('hex')
20
+
21
+ /**
22
+ * @param {(ac: AbortController) => Promise<void>} func
23
+ */
24
+ export async function runWithAbortController(func) {
25
+ let ac = new AbortController()
26
+ try {
27
+ await func(ac)
28
+ await sleep(1000)
29
+ } finally { ac.abort() }
30
+ }
31
+
32
+ /**
33
+ * @param {number} size
34
+ */
35
+ export function formatSize(size) {
36
+ if (typeof size !== 'number') return ''
37
+ if (size <= 0) { return '0B'.padStart(8, ' ') }
38
+ let companys = 'B KB MB GB TB'.split(' ')
39
+ let cur = size
40
+ while (cur >= 1024) {
41
+ companys.shift()
42
+ cur /= 1024
43
+ }
44
+ return `${formatNumber(cur)}${companys[0]}`.padStart(8, ' ')
45
+ }
46
+
47
+ /**
48
+ * @param {number} num
49
+ */
50
+ export function formatNumber(num) {
51
+ return Number(num.toFixed(2))
52
+ }
53
+
54
+ /**
55
+ * @typedef {{
56
+ * promise: Promise<any>;
57
+ * resolve: (value: any | null) => void;
58
+ * reject: (reason: any | null) => void;
59
+ * }} PromiseResolvers
60
+ */
61
+
62
+ export function Promise_withResolvers() {
63
+ /** @type{(value?:object)=>void} */
64
+ let resolve = null
65
+ /** @type{(reason?:object)=>void} */
66
+ let reject = null
67
+ const promise = new Promise((res, rej) => {
68
+ resolve = res
69
+ reject = rej
70
+ })
71
+ return { promise, resolve, reject }
72
+ }
73
+
74
+ /**
75
+ * @param {Promise<[CryptoKey,Uint8Array]>} key_iv
76
+ * @returns {TransformStream<Uint8Array, Uint8Array>}
77
+ */
78
+ export function createEncodeStream(key_iv) {
79
+ let key = null
80
+ let iv = null
81
+ return new TransformStream({
82
+ async start() {
83
+ [key, iv] = await key_iv
84
+ },
85
+ async transform(chunk, controller) {
86
+ let buffer = await buildBufferData([chunk], key, iv)
87
+ controller.enqueue(buffer)
88
+ }
89
+ })
90
+ }
91
+
92
+ /**
93
+ * @param {Promise<[CryptoKey,Uint8Array]>} key_iv
94
+ * @returns {TransformStream<Uint8Array, Uint8Array>}
95
+ */
96
+ export function createDecodeStream(key_iv) {
97
+ let key = null
98
+ let iv = null
99
+ let last = new Uint8Array(0)
100
+ return new TransformStream({
101
+ async start() {
102
+ [key, iv] = await key_iv
103
+ },
104
+ async transform(chunk, controller) {
105
+ let [queueReceive, remain] = await parseBufferData(Uint8Array_concat([last, chunk]), key, iv)
106
+ last = remain
107
+ for (const o of queueReceive) {
108
+ controller.enqueue(o)
109
+ }
110
+ }
111
+ })
112
+ }
113
+
114
+ const HEADER_CHECK = 0xb1f7705f
115
+
116
+ /**
117
+ * @param {Uint8Array[]} queue
118
+ * @param {CryptoKey} key
119
+ * @param {Uint8Array} iv
120
+ * @returns {Promise<Uint8Array>}
121
+ */
122
+ export async function buildBufferData(queue, key, iv) {
123
+ let buffers = []
124
+ for (const data of queue) {
125
+ let offset = 0
126
+ let header = new Uint8Array(8)
127
+ let headerCheck = HEADER_CHECK
128
+ let buffer = await encrypt(data, key, iv)
129
+ writeUInt32LE(header, buffer.length, offset); offset += 4
130
+ writeUInt32LE(header, headerCheck, offset); offset += 4
131
+ buffers.push(header, buffer)
132
+ }
133
+ return Uint8Array_concat(buffers)
134
+ }
135
+
136
+ /**
137
+ * @param {Uint8Array<ArrayBuffer>} buffer
138
+ * @param {CryptoKey} key
139
+ * @param {Uint8Array} iv
140
+ * @returns {Promise<[Uint8Array[],Uint8Array<ArrayBuffer>]>}
141
+ */
142
+ export async function parseBufferData(buffer, key, iv) {
143
+ /** @type{Uint8Array[]} */
144
+ let queue = []
145
+ let offset = 0
146
+ let remain = new Uint8Array(0)
147
+ while (offset < buffer.length) {
148
+ if (offset + 8 > buffer.length) {
149
+ remain = buffer.subarray(offset)
150
+ break
151
+ }
152
+ let bufferLength = readUInt32LE(buffer, offset); offset += 4
153
+ let headerCheck = readUInt32LE(buffer, offset); offset += 4
154
+ if (offset + bufferLength > buffer.length) {
155
+ remain = buffer.subarray(offset - 8)
156
+ break
157
+ }
158
+ let check = HEADER_CHECK
159
+ if (check !== headerCheck) {
160
+ remain = new Uint8Array(0)
161
+ console.error('data check error!', bufferLength, check.toString(16), headerCheck.toString(16))
162
+ break
163
+ }
164
+ let data = buffer.subarray(offset, offset + bufferLength); offset += bufferLength
165
+ let buf = await decrypt(data, key, iv)
166
+ if (buf.length > 0) {
167
+ queue.push(buf)
168
+ }
169
+ }
170
+ return [queue, remain]
171
+ }
172
+
173
+
174
+ /**
175
+ * @param {Uint8Array} buffer
176
+ * @param {number} offset
177
+ */
178
+ export function readUInt32LE(buffer, offset) {
179
+ if (offset < 0 || offset + 4 > buffer.length) throw new RangeError('Reading out of bounds')
180
+ return ((buffer[offset] & 0xff) |
181
+ ((buffer[offset + 1] & 0xff) << 8) |
182
+ ((buffer[offset + 2] & 0xff) << 16) |
183
+ ((buffer[offset + 3] & 0xff) << 24)) >>> 0 // >>> 0 to convert to unsigned
184
+ }
185
+
186
+ /**
187
+ * @param {Uint8Array} buffer
188
+ * @param {number} value
189
+ * @param {number} offset
190
+ */
191
+ export function writeUInt32LE(buffer, value, offset) {
192
+ if (offset < 0 || offset + 4 > buffer.length) throw new RangeError('Writing out of bounds')
193
+ buffer[offset] = value & 0xff
194
+ buffer[offset + 1] = (value >> 8) & 0xff
195
+ buffer[offset + 2] = (value >> 16) & 0xff
196
+ buffer[offset + 3] = (value >> 24) & 0xff
197
+ }
198
+
199
+ /**
200
+ * @param {Uint8Array[]} buffers
201
+ */
202
+ export function Uint8Array_concat(buffers) {
203
+ const totalLength = buffers.reduce((sum, buffer) => sum + buffer.length, 0)
204
+ const resultBuffer = new Uint8Array(totalLength)
205
+ let offset = 0
206
+ for (const buffer of buffers) {
207
+ resultBuffer.set(buffer, offset)
208
+ offset += buffer.length
209
+ }
210
+ return resultBuffer
211
+ }
212
+
213
+ /**
214
+ * @param {*} array
215
+ * @param {'utf-8'|'hex'|'base64'} [encoding]
216
+ */
217
+ export function Uint8Array_from(array, encoding) {
218
+ if (encoding == 'hex') {
219
+ array = new Uint8Array(array.match(/[\da-f]{2}/gi).map((h) => parseInt(h, 16)))
220
+ }
221
+ if (encoding == 'base64') {
222
+ array = Uint8Array.from(atob(array), (o) => o.codePointAt(0))
223
+ }
224
+ if (encoding == 'utf-8') {
225
+ array = new TextEncoder().encode(array)
226
+ }
227
+ if (typeof array === 'string') {
228
+ array = new TextEncoder().encode(array)
229
+ }
230
+ if (Array.isArray(array) || array instanceof Uint8Array) {
231
+ return new Uint8Array(array)
232
+ }
233
+ throw new TypeError('Argument must be an array or Uint8Array')
234
+ }
235
+
236
+ /**
237
+ * @param {Uint8Array} buffer
238
+ * @param {'utf-8' | 'hex' | 'base64'} [encoding]
239
+ */
240
+ export function Uint8Array_toString(buffer, encoding = 'utf-8') {
241
+ if (encoding == 'hex') {
242
+ return Array.from(buffer).map((b) => b.toString(16).padStart(2, "0")).join('')
243
+ }
244
+ if (encoding == 'base64') {
245
+ return btoa(String.fromCharCode(...buffer))
246
+ }
247
+ // utf-8
248
+ return new TextDecoder().decode(buffer)
249
+ }
250
+
251
+ /**
252
+ * @param {number} number
253
+ */
254
+ function buildBufferNumberUInt32LE(number) {
255
+ let buffer = new Uint8Array(4)
256
+ writeUInt32LE(buffer, number, 0)
257
+ return buffer
258
+ }
259
+
260
+ /**
261
+ * @param {string} string
262
+ */
263
+ function buildBufferSizeString(string) {
264
+ let buffer = new TextEncoder().encode(string)
265
+ return Uint8Array_concat([
266
+ buildBufferNumberUInt32LE(buffer.length),
267
+ buffer,
268
+ ])
269
+ }
270
+
271
+ /**
272
+ * @param {Uint8Array} buffer
273
+ * @param {number} offset
274
+ */
275
+ function readBufferSizeString(buffer, offset) {
276
+ let size = readUInt32LE(buffer, offset)
277
+ let start = offset + 4
278
+ let end = start + size
279
+ let string = new TextDecoder().decode(buffer.slice(start, end))
280
+ return { size: 4 + size, string }
281
+ }
282
+
283
+ export function guid() {
284
+ let buffer = new Uint8Array(16)
285
+ if (globalThis.crypto) {
286
+ crypto.getRandomValues(buffer)
287
+ } else {
288
+ for (let i = 0; i < buffer.length; i++) {
289
+ buffer[i] = Math.floor(Math.random() * 256)
290
+ }
291
+ }
292
+ return Array.from(buffer).map((o) => o.toString(16).padStart(2, '0')).join('')
293
+ }
294
+
295
+ let tcpTunnelDataRecv = 0
296
+ let tcpTunnelDataSend = 0
297
+
298
+ /**
299
+ *
300
+ * @param {TCP_TUNNEL_DATA} data
301
+ */
302
+ export function printTcpTunnelData(data) {
303
+ return `id: ${`${data.srcId}:${data.dstId}`.padEnd(10)} channel: ${`${data.srcChannel}:${data.dstChannel}`.padEnd(10)} ${{
304
+ 0xa9b398d5: 'TUNNEL_TCP_TYPE_INIT ',
305
+ 0xe41957d3: 'TUNNEL_TCP_TYPE_LISTEN ',
306
+ 0x20993e38: 'TUNNEL_TCP_TYPE_ONLISTEN ',
307
+ 0x11d949f8: 'TUNNEL_TCP_TYPE_CONNECT ',
308
+ 0x377b2181: 'TUNNEL_TCP_TYPE_ONCONNECT ',
309
+ 0x48678f39: 'TUNNEL_TCP_TYPE_DATA ',
310
+ 0x8117f762: 'TUNNEL_TCP_TYPE_ERROR ',
311
+ 0x72fd6470: 'TUNNEL_TCP_TYPE_CLOSE ',
312
+ 0x4768e1ba: 'TUNNEL_TCP_TYPE_PING ',
313
+ 0x106f43fb: 'TUNNEL_TCP_TYPE_PONG ',
314
+ 0xc5870539: 'TUNNEL_TCP_TYPE_ACK ',
315
+ }[data.type]} recv: ${(tcpTunnelDataRecv)} send: ${(tcpTunnelDataSend)} size:${data.buffer.length}`
316
+ }
317
+
318
+ /**
319
+ *
320
+ * @param {string} password
321
+ * @param {number} iterations
322
+ * @returns {Promise<[CryptoKey,Uint8Array]>}
323
+ */
324
+ export async function buildKeyIv(password, iterations) {
325
+ if (!TUNNEL_TCP_WITH_CRYPTO) return [null, null]
326
+ if (!password) return [null, null]
327
+ const keyMaterial = await crypto.subtle.importKey(
328
+ "raw",
329
+ new TextEncoder().encode(password),
330
+ "PBKDF2",
331
+ false,
332
+ ["deriveBits", "deriveKey"],
333
+ )
334
+ const salt = await crypto.subtle.digest("SHA-512", new TextEncoder().encode(password))
335
+ const pbkdf2Params = {
336
+ name: "PBKDF2",
337
+ salt,
338
+ iterations: iterations,
339
+ hash: "SHA-256",
340
+ }
341
+ const key = await crypto.subtle.deriveKey(
342
+ pbkdf2Params,
343
+ keyMaterial,
344
+ { name: "AES-GCM", length: 256 },
345
+ true,
346
+ ["encrypt", "decrypt"],
347
+ )
348
+ const iv = await crypto.subtle.deriveBits(
349
+ pbkdf2Params,
350
+ keyMaterial,
351
+ 256,
352
+ )
353
+ return [key, new Uint8Array(iv)]
354
+ }
355
+
356
+ /**
357
+ *
358
+ * @param {Uint8Array} data
359
+ * @param {CryptoKey} key
360
+ * @param {Uint8Array} iv
361
+ * @returns
362
+ */
363
+ export async function encrypt(data, key, iv) {
364
+ if (!TUNNEL_TCP_WITH_CRYPTO) return data
365
+ if (!key) return data
366
+ const encryptedData = await crypto.subtle.encrypt(
367
+ { name: 'AES-GCM', iv: iv }, key, data
368
+ )
369
+ return new Uint8Array(encryptedData)
370
+ }
371
+
372
+ /**
373
+ * @param {Uint8Array} data
374
+ * @param {CryptoKey} key
375
+ * @param {Uint8Array} iv
376
+ * @returns
377
+ */
378
+ export async function decrypt(data, key, iv) {
379
+ if (!TUNNEL_TCP_WITH_CRYPTO) return data
380
+ if (!key) return data
381
+ try {
382
+ const encryptedArray = data
383
+ const decryptedData = await crypto.subtle.decrypt(
384
+ { name: 'AES-GCM', iv: iv }, key, encryptedArray
385
+ )
386
+ return new Uint8Array(decryptedData)
387
+ } catch (error) {
388
+ console.error('decrypt error', error.message)
389
+ }
390
+ return new Uint8Array(0)
391
+ }
392
+
393
+ /**
394
+ * @param {AbortSignal} signal
395
+ * @param {()=> Promise<void>} callback
396
+ */
397
+ export async function timeWaitRetryLoop(signal, callback) {
398
+ let waitTime = 300
399
+ while (!signal.aborted) {
400
+ let time = performance.now()
401
+ try {
402
+ await callback()
403
+ } catch (error) {
404
+ console.error('timeWaitRetryLoop', error.message)
405
+ }
406
+ if (performance.now() - time > 10_000) {
407
+ waitTime = 300
408
+ }
409
+ await sleep(waitTime)
410
+ waitTime *= 2
411
+ if (waitTime > 60_000) {
412
+ waitTime = 60_000
413
+ }
414
+ }
415
+ }
416
+
417
+ export const TUNNEL_TCP_TYPE_INIT = 0xa9b398d5 // 链接建立后返回id
418
+ export const TUNNEL_TCP_TYPE_LISTEN = 0xe41957d3
419
+ export const TUNNEL_TCP_TYPE_ONLISTEN = 0x20993e38
420
+ export const TUNNEL_TCP_TYPE_CONNECT = 0x11d949f8
421
+ export const TUNNEL_TCP_TYPE_ONCONNECT = 0x377b2181
422
+ export const TUNNEL_TCP_TYPE_DATA = 0x48678f39
423
+ export const TUNNEL_TCP_TYPE_ERROR = 0x8117f762
424
+ export const TUNNEL_TCP_TYPE_CLOSE = 0x72fd6470
425
+ export const TUNNEL_TCP_TYPE_PING = 0x4768e1ba
426
+ export const TUNNEL_TCP_TYPE_PONG = 0x106f43fb
427
+ export const TUNNEL_TCP_TYPE_ACK = 0xc5870539
428
+
429
+ /**
430
+ * @typedef {TUNNEL_TCP_TYPE_INIT
431
+ * |TUNNEL_TCP_TYPE_LISTEN
432
+ * |TUNNEL_TCP_TYPE_ONLISTEN
433
+ * |TUNNEL_TCP_TYPE_CONNECT
434
+ * |TUNNEL_TCP_TYPE_ONCONNECT
435
+ * |TUNNEL_TCP_TYPE_DATA
436
+ * |TUNNEL_TCP_TYPE_ERROR
437
+ * |TUNNEL_TCP_TYPE_CLOSE
438
+ * |TUNNEL_TCP_TYPE_PING
439
+ * |TUNNEL_TCP_TYPE_PONG
440
+ * |TUNNEL_TCP_TYPE_ACK} TUNNEL_TCP_TYPE
441
+ */
442
+
443
+ /**
444
+ * @typedef {{
445
+ * type:TUNNEL_TCP_TYPE;
446
+ * srcId:number;
447
+ * dstId:number;
448
+ * srcChannel:number;
449
+ * dstChannel:number;
450
+ * buffer:Uint8Array;
451
+ * }} TCP_TUNNEL_DATA
452
+ */
453
+
454
+ /**
455
+ * @typedef {{
456
+ * key:string;
457
+ * }} TUNNEL_TCP_DATA_LISTEN
458
+ */
459
+
460
+ /**
461
+ * @typedef {{
462
+ * key:string;
463
+ * }} TUNNEL_TCP_DATA_CONNECT
464
+ */
465
+
466
+ /**
467
+ * @typedef {{
468
+ * time:number;
469
+ * }} TUNNEL_TCP_DATA_PINGPONG
470
+ */
471
+
472
+ /**
473
+ * @typedef {{
474
+ * id:number;
475
+ * encodeWriter:WritableStreamDefaultWriter<Uint8Array>;
476
+ * }} TUNNEL_TCP_SERVER
477
+ */
478
+
479
+ /**
480
+ * @typedef {{
481
+ * readable:ReadableStream<Uint8Array>;
482
+ * writable:WritableStream<Uint8Array>;
483
+ * reader:ReadableStreamDefaultReader<Uint8Array>;
484
+ * writer:WritableStreamDefaultWriter<Uint8Array>;
485
+ * dstId:number;
486
+ * }} TUNNEL_TCP_SERVER_HELPER
487
+ * @typedef {{
488
+ * readable:ReadableStream<Uint8Array>;
489
+ * writable:WritableStream<Uint8Array>;
490
+ * reader:ReadableStreamDefaultReader<Uint8Array>;
491
+ * writer:WritableStreamDefaultWriter<Uint8Array>;
492
+ * listen:(param:{
493
+ * clientKey?:string;
494
+ * tunnelKey:string;
495
+ * host?:string;
496
+ * port:number;
497
+ * })=>Promise<void>;
498
+ * connect:(param:{
499
+ * clientKey?:string;
500
+ * tunnelKey: string;
501
+ * port: number;
502
+ * })=>Promise<void>;
503
+ * }} TUNNEL_TCP_CLIENT_HELPER
504
+ */
505
+
506
+ /**
507
+ * @param {TCP_TUNNEL_DATA} box
508
+ */
509
+ export function buildTcpTunnelData(box) {
510
+ let offset = 0
511
+ let header = new Uint8Array(20)
512
+ writeUInt32LE(header, box.type, offset); offset += 4
513
+ writeUInt32LE(header, box.srcId, offset); offset += 4
514
+ writeUInt32LE(header, box.dstId, offset); offset += 4
515
+ writeUInt32LE(header, box.srcChannel, offset); offset += 4
516
+ writeUInt32LE(header, box.dstChannel, offset); offset += 4
517
+ return Uint8Array_concat([header, box.buffer,])
518
+ }
519
+
520
+ /**
521
+ * @param {Uint8Array} buffer
522
+ */
523
+ export function parseTcpTunnelData(buffer) {
524
+ let offset = 0
525
+ /** @type{*} */
526
+ let type = readUInt32LE(buffer, offset); offset += 4
527
+ let src_id = readUInt32LE(buffer, offset); offset += 4
528
+ let dst_id = readUInt32LE(buffer, offset); offset += 4
529
+ let src_channel = readUInt32LE(buffer, offset); offset += 4
530
+ let dst_channel = readUInt32LE(buffer, offset); offset += 4
531
+ let data = buffer.subarray(offset)
532
+ /** @type{TCP_TUNNEL_DATA} */
533
+ let box = { type, srcId: src_id, dstId: dst_id, srcChannel: src_channel, dstChannel: dst_channel, buffer: data }
534
+ return box
535
+ }
536
+
537
+ /**
538
+ * @param {number} bufferTime
539
+ */
540
+ export function createTimeBufferedTransformStream(bufferTime) {
541
+ if (!TUNNEL_TCP_TIME_BUFFERED) {
542
+ return new TransformStream()
543
+ }
544
+ let maxloop = Math.floor(Math.max(5_000 / bufferTime, 3))
545
+ /** @type{()=>void} */
546
+ let callback = null
547
+ const runCallback = () => {
548
+ if (callback != null) {
549
+ callback()
550
+ callback = null
551
+ }
552
+ if (loop-- < 0) {
553
+ clean()
554
+ }
555
+ }
556
+ let loop = 0
557
+ let queue = []
558
+ let time = performance.now()
559
+ let timer = null
560
+ let transform = new TransformStream({
561
+ async transform(chunk, controller) {
562
+ if (chunk.length < 100) {
563
+ loop += chunk.length
564
+ if (loop > maxloop) {
565
+ loop = maxloop
566
+ }
567
+ if (timer == null) {
568
+ timer = setInterval(runCallback, bufferTime)
569
+ console.info('create loop timer')
570
+ }
571
+ }
572
+ if (timer == null || chunk.length > 1024) {
573
+ runCallback()
574
+ controller.enqueue(chunk)
575
+ return
576
+ }
577
+ queue.push(chunk)
578
+ callback = () => {
579
+ if (queue.length > 0) {
580
+ controller.enqueue(Uint8Array_concat(queue))
581
+ queue = []
582
+ time = performance.now()
583
+ }
584
+ }
585
+ if (performance.now() - time > bufferTime) {
586
+ runCallback()
587
+ }
588
+ },
589
+ flush() {
590
+ clean()
591
+ }
592
+ })
593
+
594
+ const clean = () => {
595
+ if (timer == null) {
596
+ return
597
+ }
598
+ console.info('clean loop timer')
599
+ clearInterval(timer)
600
+ timer = null
601
+ runCallback()
602
+ }
603
+
604
+ return transform
605
+ }
606
+
607
+ /**
608
+ * @param {Map<number,SocketChannel>} channelMap
609
+ * @param {number} channelId
610
+ * @param {WritableStreamDefaultWriter<Uint8Array>} encodeWriter
611
+ */
612
+ export function pipeSocketDataWithChannel(channelMap, channelId, encodeWriter) {
613
+ let channel = channelMap.get(channelId)
614
+ let socket = channel.socket
615
+ let signal = Promise_withResolvers()
616
+ signal.resolve()
617
+ let sendPackSize = 0
618
+ let recvPackSize = 0
619
+ channel.notify = (size) => {
620
+ recvPackSize = size
621
+ signal.resolve()
622
+ }
623
+ let [clientKey, clientIv] = channel.key_iv
624
+ let bufferedTransform = createTimeBufferedTransformStream(50)
625
+ Readable.toWeb(socket).pipeThrough(bufferedTransform).pipeTo(new WritableStream({
626
+ async write(chunk) {
627
+ const buffer = await encrypt(chunk, clientKey, clientIv)
628
+ let bufferPackSize = sendPackSize - recvPackSize
629
+ if (bufferPackSize > 10) {
630
+ signal.resolve()
631
+ signal = Promise_withResolvers()
632
+ if (DEBUG_TUNNEL_TCP) {
633
+ console.info('stop wait signal', ' sendPackSize:', sendPackSize, ' recvPackSize:', recvPackSize, ' bufferPackSize:', bufferPackSize)
634
+ }
635
+ }
636
+ await signal.promise
637
+ await encodeWriter.write(buildTcpTunnelData({
638
+ type: TUNNEL_TCP_TYPE_DATA,
639
+ srcId: channel.srcId,
640
+ srcChannel: channel.srcChannel,
641
+ dstId: channel.dstId,
642
+ dstChannel: channel.dstChannel,
643
+ buffer: buffer,
644
+ })).catch((err) => { console.error('web stream write error', err.message) })
645
+ sendPackSize++
646
+ },
647
+ async close() {
648
+ await encodeWriter.write(buildTcpTunnelData({
649
+ type: TUNNEL_TCP_TYPE_CLOSE,
650
+ srcId: channel.srcId,
651
+ srcChannel: channel.srcChannel,
652
+ dstId: channel.dstId,
653
+ dstChannel: channel.dstChannel,
654
+ buffer: new Uint8Array(0),
655
+ })).catch((err) => { console.error('web stream write error', err.message) })
656
+ channelMap.delete(channelId)
657
+ }
658
+ })).catch((err) => {
659
+ console.error('web stream error', err.message)
660
+ })
661
+ socket.on('error', (err) => {
662
+ console.error('pipeSocketDataWithChannel on error ', err.message)
663
+ encodeWriter.write(buildTcpTunnelData({
664
+ type: TUNNEL_TCP_TYPE_ERROR,
665
+ srcId: channel.srcId,
666
+ srcChannel: channel.srcChannel,
667
+ dstId: channel.dstId,
668
+ dstChannel: channel.dstChannel,
669
+ buffer: new Uint8Array(0),
670
+ })).catch((err) => { console.error('web stream write error', err.message) })
671
+ channelMap.delete(channelId)
672
+ })
673
+ }
674
+
675
+ /**
676
+ * @param {TunnelTcpServerHelperParam} param
677
+ * @param {TCP_TUNNEL_DATA} data
678
+ */
679
+ function natTunnelData(param, data) {
680
+ // if (data.dstId != param.serverId) {
681
+ // return
682
+ // }
683
+ // /** @type{Map<number,number>} */
684
+ // let natId = new Map()
685
+ // /** @type{Map<number,number>} */
686
+ // let natChannel = new Map()
687
+ // data.dstId = natId.get(data.dstId)
688
+ // data.dstChannel = natChannel.get(data.dstChannel)
689
+ // data.srcId = natId.get(data.srcId)
690
+ // data.srcChannel = natChannel.get(data.srcChannel)
691
+ }
692
+
693
+ /**
694
+ * @param {TunnelTcpServerHelperParam} param
695
+ * @param {WritableStreamDefaultWriter<Uint8Array<ArrayBufferLike>>} encodeWriter
696
+ * @param {Uint8Array<ArrayBufferLike>} chunk
697
+ */
698
+ async function dispatchServerBufferData(param, encodeWriter, chunk) {
699
+ let data = parseTcpTunnelData(chunk)
700
+ if (DEBUG_TUNNEL_TCP) {
701
+ tcpTunnelDataRecv += chunk.length
702
+ console.info('recv', printTcpTunnelData(data))
703
+ }
704
+ natTunnelData(param, data)
705
+ if (data.type == TUNNEL_TCP_TYPE_LISTEN) {
706
+ /** @type{TUNNEL_TCP_DATA_LISTEN} */
707
+ let o = JSON.parse(Uint8Array_toString(data.buffer))
708
+ param.listenMap.set(o.key, data.srcId)
709
+ data.type = TUNNEL_TCP_TYPE_ONLISTEN
710
+ data.dstId = data.srcId
711
+ }
712
+
713
+ if (data.type == TUNNEL_TCP_TYPE_CONNECT) {
714
+ /** @type{TUNNEL_TCP_DATA_CONNECT} */
715
+ let o = JSON.parse(Uint8Array_toString(data.buffer))
716
+ let dstId = param.listenMap.get(o.key)
717
+ data.dstId = dstId
718
+ if (!param.dstMap.has(dstId)) {
719
+ param.listenMap.delete(o.key)
720
+ }
721
+ }
722
+
723
+ let dstWriter = param.dstMap.get(data.dstId)
724
+ if (!dstWriter) {
725
+ data.type = TUNNEL_TCP_TYPE_CLOSE
726
+ data.dstId = data.srcId
727
+ data.dstChannel = data.srcChannel
728
+ let srcWriter = param.dstMap.get(data.dstId)
729
+ if (srcWriter) {
730
+ await srcWriter.encodeWriter.write(buildTcpTunnelData(data))
731
+ } else {
732
+ await encodeWriter.write(buildTcpTunnelData(data))
733
+ }
734
+ } else {
735
+ await dstWriter.encodeWriter.write(buildTcpTunnelData(data))
736
+ }
737
+ }
738
+
739
+ /**
740
+ * @param {TunnelTcpClientHelperParam} param
741
+ * @param {{ (): Promise<void>; }} setup
742
+ * @param {Map<string,{host:string;port:number;key_iv:[CryptoKey, Uint8Array]}>} listenKeyParamMap
743
+ * @param {Map<number, SocketChannel>} channelMap
744
+ * @param {WritableStreamDefaultWriter<Uint8Array<ArrayBufferLike>>} encodeWriter
745
+ * @param {Uint8Array<ArrayBufferLike>} buffer
746
+ */
747
+ async function dispatchClientBufferData(param, setup, listenKeyParamMap, channelMap, encodeWriter, buffer) {
748
+ let data = parseTcpTunnelData(buffer)
749
+ if (DEBUG_TUNNEL_TCP) {
750
+ tcpTunnelDataRecv += buffer.length
751
+ console.info('recv', printTcpTunnelData(data))
752
+ }
753
+ if (data.type == TUNNEL_TCP_TYPE_INIT) {
754
+ param.clientDataId = data.dstId
755
+ await setup()
756
+ }
757
+ if (data.type == TUNNEL_TCP_TYPE_ONLISTEN) {
758
+ }
759
+ if (data.type == TUNNEL_TCP_TYPE_CONNECT) {
760
+ /** @type{TUNNEL_TCP_DATA_CONNECT} */
761
+ let o = JSON.parse(Uint8Array_toString(data.buffer))
762
+ let { host, port, key_iv } = listenKeyParamMap.get(o.key)
763
+ let connectSocket = net.createConnection({ host: host || '127.0.0.1', port: port })
764
+ let channelId = param.uniqueId++
765
+ /** @type{SocketChannel} */
766
+ let channel = {
767
+ writer: Writable.toWeb(connectSocket).getWriter(),
768
+ socket: connectSocket,
769
+ srcId: data.dstId,
770
+ dstId: data.srcId,
771
+ srcChannel: channelId,
772
+ dstChannel: data.srcChannel,
773
+ recvPackSize: 0,
774
+ key_iv,
775
+ notify: null,
776
+ }
777
+ channelMap.set(channelId, channel)
778
+ connectSocket.on('connect', () => {
779
+ encodeWriter.write(buildTcpTunnelData({
780
+ type: TUNNEL_TCP_TYPE_ONCONNECT,
781
+ srcId: channel.srcId,
782
+ srcChannel: channel.srcChannel,
783
+ dstId: channel.dstId,
784
+ dstChannel: channel.dstChannel,
785
+ buffer: data.buffer
786
+ }))
787
+ })
788
+ pipeSocketDataWithChannel(channelMap, channelId, encodeWriter)
789
+ }
790
+ if (data.type == TUNNEL_TCP_TYPE_ONCONNECT) {
791
+ let channelId = data.dstChannel
792
+ let channel = channelMap.get(channelId)
793
+ if (channel) {
794
+ channel.dstId = data.srcId
795
+ channel.dstChannel = data.srcChannel
796
+ pipeSocketDataWithChannel(channelMap, channelId, encodeWriter)
797
+ /** @type{TUNNEL_TCP_DATA_PINGPONG} */
798
+ let pingData = { time: Date.now() }
799
+ await encodeWriter.write(buildTcpTunnelData({
800
+ type: TUNNEL_TCP_TYPE_PING,
801
+ srcId: channel.srcId,
802
+ srcChannel: channel.srcChannel,
803
+ dstId: channel.dstId,
804
+ dstChannel: channel.dstChannel,
805
+ buffer: Uint8Array_from(JSON.stringify(pingData)),
806
+ }))
807
+ } else {
808
+ await closeRemoteChannel(encodeWriter, data)
809
+ }
810
+ }
811
+ if (data.type == TUNNEL_TCP_TYPE_PING) {
812
+ let channelId = data.dstChannel
813
+ let channel = channelMap.get(channelId)
814
+ if (channel) {
815
+ channel.srcId = data.dstId
816
+ channel.dstId = data.srcId
817
+ await encodeWriter.write(buildTcpTunnelData({
818
+ type: TUNNEL_TCP_TYPE_PONG,
819
+ srcId: channel.srcId,
820
+ srcChannel: channel.srcChannel,
821
+ dstId: channel.dstId,
822
+ dstChannel: channel.dstChannel,
823
+ buffer: data.buffer,
824
+ }))
825
+ } else {
826
+ await closeRemoteChannel(encodeWriter, data)
827
+ }
828
+ }
829
+ if (data.type == TUNNEL_TCP_TYPE_PONG) {
830
+ let channelId = data.dstChannel
831
+ let channel = channelMap.get(channelId)
832
+ if (channel) {
833
+ channel.srcId = data.dstId
834
+ channel.dstId = data.srcId
835
+ /** @type{TUNNEL_TCP_DATA_PINGPONG} */
836
+ let pingData = JSON.parse(Uint8Array_toString(data.buffer))
837
+ console.info('createTunnelTcpClientHelper ', 'ping time', (Date.now() - pingData.time))
838
+ } else {
839
+ await closeRemoteChannel(encodeWriter, data)
840
+ }
841
+ }
842
+ if (data.type == TUNNEL_TCP_TYPE_CLOSE) {
843
+ let channelId = data.dstChannel
844
+ let channel = channelMap.get(channelId)
845
+ if (channel) {
846
+ channelMap.delete(channelId)
847
+ channel.socket.end()
848
+ }
849
+ }
850
+ if (data.type == TUNNEL_TCP_TYPE_ERROR) {
851
+ let channelId = data.dstChannel
852
+ let channel = channelMap.get(channelId)
853
+ if (channel) {
854
+ channelMap.delete(channelId)
855
+ channel.socket.destroy()
856
+ }
857
+ }
858
+ if (data.type == TUNNEL_TCP_TYPE_DATA) {
859
+ let channelId = data.dstChannel
860
+ let channel = channelMap.get(channelId)
861
+ if (channel) {
862
+ channel.srcId = data.dstId
863
+ channel.dstId = data.srcId
864
+ let [clientKey, clientIv] = channel.key_iv
865
+ let buffer = await decrypt(data.buffer, clientKey, clientIv)
866
+ if (buffer.length > 0) {
867
+ await channel.writer.write(buffer)
868
+ }
869
+ channel.recvPackSize++
870
+ await sendAck(encodeWriter, channel)
871
+ } else {
872
+ await closeRemoteChannel(encodeWriter, data)
873
+ }
874
+ }
875
+ if (data.type == TUNNEL_TCP_TYPE_ACK) {
876
+ let channelId = data.dstChannel
877
+ let channel = channelMap.get(channelId)
878
+ if (channel) {
879
+ channel.srcId = data.dstId
880
+ channel.dstId = data.srcId
881
+ let size = readUInt32LE(data.buffer, 0)
882
+ channel.notify(size)
883
+ } else {
884
+ await closeRemoteChannel(encodeWriter, data)
885
+ }
886
+ }
887
+ }
888
+
889
+ /**
890
+ * @param {WritableStreamDefaultWriter<Uint8Array>} encodeWriter
891
+ * @param {SocketChannel} channel
892
+ */
893
+ export async function sendAck(encodeWriter, channel) {
894
+ if (channel.recvPackSize % 5 != 0) {
895
+ return
896
+ }
897
+ let sizeBuffer = new Uint8Array(4)
898
+ writeUInt32LE(sizeBuffer, channel.recvPackSize, 0)
899
+ await encodeWriter.write(buildTcpTunnelData({
900
+ type: TUNNEL_TCP_TYPE_ACK,
901
+ srcId: channel.srcId,
902
+ srcChannel: channel.srcChannel,
903
+ dstId: channel.dstId,
904
+ dstChannel: channel.dstChannel,
905
+ buffer: sizeBuffer,
906
+ }))
907
+ }
908
+
909
+ /**
910
+ * @param {WritableStreamDefaultWriter<Uint8Array>} encodeWriter
911
+ * @param {TCP_TUNNEL_DATA} data
912
+ */
913
+ export async function closeRemoteChannel(encodeWriter, data) {
914
+ await encodeWriter.write(buildTcpTunnelData({
915
+ type: TUNNEL_TCP_TYPE_CLOSE,
916
+ srcId: data.dstId,
917
+ srcChannel: data.dstChannel,
918
+ dstId: data.srcId,
919
+ dstChannel: data.srcChannel,
920
+ buffer: new Uint8Array(0),
921
+ })).catch((err) => { console.error('closeRemoteChannel stream write error', err.message) })
922
+ }
923
+
924
+ /**
925
+ * @typedef {{
926
+ * writer:WritableStreamDefaultWriter<Uint8Array>;
927
+ * socket:net.Socket;
928
+ * srcId:number;
929
+ * dstId:number;
930
+ * srcChannel:number;
931
+ * dstChannel:number;
932
+ * notify:(size:number)=>void;
933
+ * recvPackSize:number;
934
+ * key_iv:[CryptoKey, Uint8Array];
935
+ * }} SocketChannel
936
+ */
937
+
938
+
939
+ /**
940
+ * @typedef {{
941
+ * uniqueId:number;
942
+ * listenMap:Map<string,number>;
943
+ * dstMap:Map<number,TUNNEL_TCP_SERVER>;
944
+ * serverKey?:string;
945
+ * }} TunnelTcpServerHelperParam
946
+ */
947
+
948
+ /**
949
+ * @typedef {{
950
+ * signal:AbortSignal;
951
+ * serverKey?:string;
952
+ * uniqueId:number;
953
+ * clientDataId:number;
954
+ * }} TunnelTcpClientHelperParam
955
+ */
956
+
957
+ /**
958
+ * @param {TunnelTcpServerHelperParam} param
959
+ */
960
+ export function createTunnelTcpServerHelper(param) {
961
+ let server_key_iv = buildKeyIv(param.serverKey, 10)
962
+ let encode = createEncodeStream(server_key_iv)
963
+ let decode = createDecodeStream(server_key_iv)
964
+ let encodeWriter = encode.writable.getWriter()
965
+
966
+ if (DEBUG_TUNNEL_TCP) {
967
+ let writer = encodeWriter
968
+ encodeWriter = new WritableStream({
969
+ async write(chunk) {
970
+ tcpTunnelDataSend += chunk.length
971
+ let data = parseTcpTunnelData(chunk)
972
+ console.info('send', printTcpTunnelData(data))
973
+ writer.write(chunk)
974
+ }
975
+ }).getWriter()
976
+ }
977
+
978
+ decode.readable.pipeTo(new WritableStream({
979
+ async write(chunk) {
980
+ try {
981
+ await dispatchServerBufferData(param, encodeWriter, chunk)
982
+ } catch (error) {
983
+ console.error('decode.readable.pipeTo.write', error.message)
984
+ }
985
+ }
986
+ }))
987
+
988
+ let id = param.uniqueId++
989
+ param.dstMap.set(id, { id, encodeWriter: encodeWriter })
990
+ encodeWriter.write(buildTcpTunnelData({
991
+ type: TUNNEL_TCP_TYPE_INIT,
992
+ srcId: 0,
993
+ srcChannel: 0,
994
+ dstId: id,
995
+ dstChannel: 0,
996
+ buffer: new Uint8Array(0),
997
+ }))
998
+
999
+ /** @type{TUNNEL_TCP_SERVER_HELPER} */
1000
+ let helper = { readable: encode.readable, writable: decode.writable, reader: null, writer: null, dstId: id, }
1001
+ return helper
1002
+ }
1003
+
1004
+ /**
1005
+ * @param {TunnelTcpClientHelperParam} param
1006
+ */
1007
+ export function createTunnelTcpClientHelper(param) {
1008
+ /** @type{Map<number,SocketChannel>} */
1009
+ let channelMap = new Map()
1010
+
1011
+ /** @type{Map<string,{host:string;port:number;key_iv:[CryptoKey, Uint8Array]}>} */
1012
+ let listenKeyParamMap = new Map()
1013
+
1014
+ let server_key_iv = buildKeyIv(param.serverKey, 10)
1015
+
1016
+ param.signal.addEventListener('abort', () => {
1017
+ channelMap.values().forEach(o => {
1018
+ o.socket.destroy()
1019
+ })
1020
+ })
1021
+
1022
+ let encode = createEncodeStream(server_key_iv)
1023
+ let decode = createDecodeStream(server_key_iv)
1024
+ let encodeWriter = encode.writable.getWriter()
1025
+ if (DEBUG_TUNNEL_TCP) {
1026
+ let writer = encodeWriter
1027
+ encodeWriter = new WritableStream({
1028
+ async write(chunk) {
1029
+ tcpTunnelDataSend += chunk.length
1030
+ let data = parseTcpTunnelData(chunk)
1031
+ console.info('send', printTcpTunnelData(data))
1032
+ writer.write(chunk)
1033
+ }
1034
+ }).getWriter()
1035
+ }
1036
+
1037
+ decode.readable.pipeTo(new WritableStream({
1038
+ async write(buffer) {
1039
+ try {
1040
+ await dispatchClientBufferData(param, setup, listenKeyParamMap, channelMap, encodeWriter, buffer)
1041
+ } catch (error) {
1042
+ console.error('decode.readable.pipeTo.write', error.message)
1043
+ }
1044
+ }
1045
+ }))
1046
+
1047
+ let outParam = param
1048
+ let listenParams = new Set()
1049
+
1050
+ async function setup() {
1051
+ channelMap.forEach((channel) => {
1052
+ channel.srcId = param.clientDataId
1053
+ /** @type{TUNNEL_TCP_DATA_PINGPONG} */
1054
+ let pingData = { time: Date.now() }
1055
+ encodeWriter.write(buildTcpTunnelData({
1056
+ type: TUNNEL_TCP_TYPE_PING,
1057
+ srcId: channel.srcId,
1058
+ srcChannel: channel.srcChannel,
1059
+ dstId: channel.dstId,
1060
+ dstChannel: channel.dstChannel,
1061
+ buffer: Uint8Array_from(JSON.stringify(pingData)),
1062
+ }))
1063
+ })
1064
+ for (const param of listenParams) {
1065
+ await listen(param)
1066
+ }
1067
+ }
1068
+
1069
+ /**
1070
+ * @param {{
1071
+ * clientKey?:string;
1072
+ * tunnelKey:string;
1073
+ * host?:string;
1074
+ * port:number;
1075
+ * }} param
1076
+ */
1077
+ async function listen(param) {
1078
+ listenParams.add(param)
1079
+ console.info('listenParams size', listenParams.size)
1080
+ if (outParam.clientDataId < 1) {
1081
+ console.info('skip send listen dataId == 0')
1082
+ return
1083
+ }
1084
+ let key = sha512(param.tunnelKey)
1085
+ let key_iv = await buildKeyIv(param.clientKey, 10)
1086
+ listenKeyParamMap.set(key, { host: param.host, port: param.port, key_iv })
1087
+ /** @type{TUNNEL_TCP_DATA_LISTEN} */
1088
+ let listenData = { key: key }
1089
+ await encodeWriter.write(buildTcpTunnelData({
1090
+ type: TUNNEL_TCP_TYPE_LISTEN,
1091
+ srcId: outParam.clientDataId,
1092
+ srcChannel: 0,
1093
+ dstId: 0,
1094
+ dstChannel: 0,
1095
+ buffer: Uint8Array_from(JSON.stringify(listenData)),
1096
+ }))
1097
+ }
1098
+
1099
+ /**
1100
+ * @param {{
1101
+ * clientKey?:string;
1102
+ * tunnelKey: string;
1103
+ * port: number;
1104
+ * }} param
1105
+ */
1106
+ async function connect(param) {
1107
+ let key_iv = await buildKeyIv(param.clientKey, 10)
1108
+ let server = net.createServer((socket) => {
1109
+ let channelId = outParam.uniqueId++
1110
+ socket.on('error', (err) => {
1111
+ console.error('createTunnelTcpClientHelper on socket error', err.message)
1112
+ channelMap.delete(channelId)
1113
+ })
1114
+ /** @type{TUNNEL_TCP_DATA_CONNECT} */
1115
+ let connectData = { key: sha512(param.tunnelKey) }
1116
+ /** @type{SocketChannel} */
1117
+ let channel = {
1118
+ writer: Writable.toWeb(socket).getWriter(),
1119
+ socket,
1120
+ srcId: outParam.clientDataId,
1121
+ srcChannel: channelId,
1122
+ dstId: 0,
1123
+ dstChannel: 0,
1124
+ recvPackSize: 0,
1125
+ key_iv,
1126
+ notify: null,
1127
+ }
1128
+ channelMap.set(channelId, channel)
1129
+ encodeWriter.write(buildTcpTunnelData({
1130
+ type: TUNNEL_TCP_TYPE_CONNECT,
1131
+ srcId: channel.srcId,
1132
+ srcChannel: channel.srcChannel,
1133
+ dstId: channel.dstId,
1134
+ dstChannel: channel.dstChannel,
1135
+ buffer: Uint8Array_from(JSON.stringify(connectData)),
1136
+ }))
1137
+ }).listen(param.port)
1138
+ server.on('error', (err) => {
1139
+ console.error('createTunnelTcpClientHelper connect on server error', err.message)
1140
+ })
1141
+ outParam.signal.addEventListener('abort', () => { server.close() })
1142
+ }
1143
+
1144
+ /** @type{TUNNEL_TCP_CLIENT_HELPER} */
1145
+ let helper = { readable: encode.readable, writable: decode.writable, reader: null, writer: null, listen, connect }
1146
+ return helper
1147
+
1148
+ }
1149
+
1150
+
1151
+ /**
1152
+ * @param {{
1153
+ * signal:AbortSignal;
1154
+ * serverKey:string;
1155
+ * port:number
1156
+ * }} param
1157
+ */
1158
+ export function createTunnelTcpServerSocket(param) {
1159
+ /** @type{TunnelTcpServerHelperParam} */
1160
+ let helperParam = {
1161
+ serverKey: param.serverKey,
1162
+ uniqueId: 1,
1163
+ listenMap: new Map(),
1164
+ dstMap: new Map(),
1165
+ }
1166
+ let server = net.createServer(async (socket) => {
1167
+ let helper = createTunnelTcpServerHelper(helperParam)
1168
+ helper.readable.pipeTo(Writable.toWeb(socket)).catch((err) => {
1169
+ console.error('web stream error', err.message)
1170
+ })
1171
+ Readable.toWeb(socket).pipeTo(helper.writable).catch((err) => {
1172
+ console.error('web stream error', err.message)
1173
+ })
1174
+ socket.on('end', () => {
1175
+ console.info('createTunnelTcpServerSocket socket on end')
1176
+ helperParam.dstMap.delete(helper.dstId)
1177
+ })
1178
+ socket.on('close', () => {
1179
+ console.info('createTunnelTcpServerSocket socket on close')
1180
+ helperParam.dstMap.delete(helper.dstId)
1181
+ })
1182
+ socket.on('error', (err) => {
1183
+ console.error('createTunnelTcpServerSocket socket on error', err.message)
1184
+ helperParam.dstMap.delete(helper.dstId)
1185
+ })
1186
+ }).listen(param.port)
1187
+ param.signal.addEventListener('abort', () => {
1188
+ server.close()
1189
+ })
1190
+ return server
1191
+ }
1192
+
1193
+ /**
1194
+ * @param {{
1195
+ * signal:AbortSignal;
1196
+ * serverKey?:string;
1197
+ * serverHost:string;
1198
+ * serverPort:number;
1199
+ * }} param
1200
+ */
1201
+ export function createTunnelTcpClientSocket(param) {
1202
+ let helper = createTunnelTcpClientHelper({
1203
+ serverKey: param.serverKey,
1204
+ uniqueId: 1,
1205
+ clientDataId: 0,
1206
+ signal: param.signal,
1207
+ })
1208
+ helper.writer = helper.writable.getWriter()
1209
+ let signal = Promise_withResolvers()
1210
+ /** @type{WritableStreamDefaultWriter<Uint8Array>} */
1211
+ let socketWriter = null
1212
+ helper.readable.pipeTo(new WritableStream({
1213
+ async write(chunk) {
1214
+ while (!param.signal.aborted && socketWriter == null) {
1215
+ await signal.promise
1216
+ }
1217
+ if (!param.signal.aborted) {
1218
+ await socketWriter.write(chunk)
1219
+ }
1220
+ }
1221
+ }))
1222
+ async function connectSocket() {
1223
+ let promise = Promise_withResolvers()
1224
+ let socket = net.createConnection({
1225
+ host: param.serverHost,
1226
+ port: param.serverPort,
1227
+ })
1228
+ socket.once('connect', () => {
1229
+ socketWriter = Writable.toWeb(socket).getWriter()
1230
+ Readable.toWeb(socket).pipeTo(new WritableStream({
1231
+ async write(chunk) {
1232
+ await helper.writer.write(chunk)
1233
+ }
1234
+ })).catch((err) => { console.error('web stream error', err.message) })
1235
+ signal.resolve()
1236
+ })
1237
+ socket.on('error', (err) => {
1238
+ console.error('createTunnelTcpClientSocket on error', err.message)
1239
+ promise.resolve()
1240
+ })
1241
+ socket.on('close', (err) => {
1242
+ console.info('createTunnelTcpClientSocket on close')
1243
+ promise.resolve()
1244
+ })
1245
+ const listenerAC = () => { socket.destroy() }
1246
+ param.signal.addEventListener('abort', listenerAC)
1247
+ await promise.promise
1248
+ param.signal.removeEventListener('abort', listenerAC)
1249
+ socketWriter = null
1250
+ signal.resolve()
1251
+ signal = Promise_withResolvers()
1252
+ }
1253
+ timeWaitRetryLoop(param.signal, async () => {
1254
+ console.info('createTunnelTcpClientSocket timeWaitRetryLoop', 'connectSocket')
1255
+ await connectSocket()
1256
+ })
1257
+
1258
+ return helper
1259
+ }
1260
+
1261
+
1262
+ /**
1263
+ * @param {{
1264
+ * path:string;
1265
+ * wss:WebSocketServer;
1266
+ * signal:AbortSignal;
1267
+ * serverKey:string;
1268
+ * }} param
1269
+ */
1270
+ export function createTunnelTcpServerWebSocket(param) {
1271
+ /** @type{TunnelTcpServerHelperParam} */
1272
+ let helperParam = {
1273
+ serverKey: param.serverKey,
1274
+ uniqueId: 1,
1275
+ listenMap: new Map(),
1276
+ dstMap: new Map(),
1277
+ }
1278
+ const wss = param.wss
1279
+ wss.on('connection', (ws, req) => {
1280
+ if (req.url !== param.path) {
1281
+ console.error('valid path error', req.url)
1282
+ ws.close()
1283
+ return
1284
+ }
1285
+ let helper = createTunnelTcpServerHelper(helperParam)
1286
+ helper.writer = helper.writable.getWriter()
1287
+ helper.readable.pipeTo(new WritableStream({
1288
+ async write(chunk) {
1289
+ await new Promise((resolve) => {
1290
+ ws.send(chunk, resolve)
1291
+ })
1292
+ }
1293
+ }))
1294
+
1295
+ ws.on('message', async (/**@type{*}*/buffer) => {
1296
+ if (helper.writer.desiredSize <= 0) {
1297
+ ws.pause()
1298
+ }
1299
+ await helper.writer.write(buffer)
1300
+ ws.resume()
1301
+ })
1302
+ ws.on('end', () => {
1303
+ console.info('createTunnelTcpServerWebSocket connection ws on end')
1304
+ helperParam.dstMap.delete(helper.dstId)
1305
+ })
1306
+ ws.on('close', (code) => {
1307
+ console.info('createTunnelTcpServerWebSocket connection ws on close', code)
1308
+ helperParam.dstMap.delete(helper.dstId)
1309
+ })
1310
+ ws.on('error', (err) => {
1311
+ console.error('createTunnelTcpServerWebSocket connection ws on error', err.message)
1312
+ helperParam.dstMap.delete(helper.dstId)
1313
+ })
1314
+ })
1315
+ }
1316
+
1317
+ /**
1318
+ * @param {{
1319
+ * signal:AbortSignal;
1320
+ * serverKey:string;
1321
+ * url:string;
1322
+ * }} param
1323
+ */
1324
+ export function createTunnelTcpClientWebSocket(param) {
1325
+ let helper = createTunnelTcpClientHelper({
1326
+ serverKey: param.serverKey,
1327
+ uniqueId: 1,
1328
+ clientDataId: 0,
1329
+ signal: param.signal,
1330
+ })
1331
+ helper.writer = helper.writable.getWriter()
1332
+ let signal = Promise_withResolvers()
1333
+ /** @type{WritableStreamDefaultWriter<Uint8Array>} */
1334
+ let socketWriter = null
1335
+ helper.readable.pipeTo(new WritableStream({
1336
+ async write(chunk) {
1337
+ while (!param.signal.aborted && socketWriter == null) {
1338
+ await signal.promise
1339
+ }
1340
+ if (!param.signal.aborted) {
1341
+ await socketWriter.write(chunk)
1342
+ }
1343
+ }
1344
+ }))
1345
+
1346
+ async function connectWebSocket() {
1347
+ let promise = Promise_withResolvers()
1348
+ const ws = new WebSocket(param.url)
1349
+ ws.addEventListener('open', () => {
1350
+ socketWriter = new WritableStream({
1351
+ async write(chunk) {
1352
+ ws.send(chunk)
1353
+ }
1354
+ }).getWriter()
1355
+ ws.addEventListener('message', async (ev) => {
1356
+ let buffer = await ev.data.arrayBuffer()
1357
+ await helper.writer.write(new Uint8Array(buffer))
1358
+ })
1359
+ signal.resolve()
1360
+ })
1361
+ ws.addEventListener('error', (ev) => {
1362
+ console.error('createTunnelTcpClientWebSocket connectWebSocket on error')
1363
+ promise.resolve()
1364
+ })
1365
+ ws.addEventListener('close', (ev) => {
1366
+ console.info('createTunnelTcpClientWebSocket connectWebSocket on close')
1367
+ promise.resolve()
1368
+ })
1369
+ const listenerAC = () => { ws.close() }
1370
+ param.signal.addEventListener('abort', listenerAC)
1371
+ await promise.promise
1372
+ param.signal.removeEventListener('abort', listenerAC)
1373
+ socketWriter = null
1374
+ signal.resolve()
1375
+ signal = Promise_withResolvers()
1376
+ }
1377
+
1378
+ timeWaitRetryLoop(param.signal, async () => {
1379
+ console.info('createTunnelTcpClientWebSocket timeWaitRetryLoop', 'connectWebSocket')
1380
+ await connectWebSocket()
1381
+ })
1382
+
1383
+ return helper
1384
+ }
1385
+
1386
+ /**
1387
+ * @param {{
1388
+ * path:string;
1389
+ * router:Router<any, {}>;
1390
+ * signal:AbortSignal;
1391
+ * serverKey?:string;
1392
+ * }} param
1393
+ */
1394
+ export function createTunnelTcpServerKoaRouter(param) {
1395
+ /** @type{TunnelTcpServerHelperParam} */
1396
+ let helperParam = {
1397
+ serverKey: param.serverKey,
1398
+ uniqueId: 1,
1399
+ listenMap: new Map(),
1400
+ dstMap: new Map(),
1401
+ }
1402
+
1403
+ param.router.post(param.path, async (ctx) => {
1404
+ console.info('clientId:', 'createTunnelTcpServerKoaRouter on post ' + param.path)
1405
+ let helper = createTunnelTcpServerHelper(helperParam)
1406
+ helper.writer = helper.writable.getWriter()
1407
+ helper.reader = helper.readable.getReader()
1408
+ ctx.req.on('error', (e) => { console.error('createTunnelTcpServerKoaRouter in req error', e.message) })
1409
+ await Readable.toWeb(ctx.req).pipeTo(new WritableStream({
1410
+ async write(chunk) {
1411
+ await helper.writer.write(chunk)
1412
+ }
1413
+ })).catch((err) => { console.error('web stream error', err.message) })
1414
+
1415
+ ctx.status = 200
1416
+ ctx.response.set({
1417
+ 'Cache-Control': 'no-cache',
1418
+ 'Content-Type': 'application/octet-stream'
1419
+ })
1420
+ ctx.body = Readable.fromWeb(new ReadableStream({
1421
+ async pull(controller) {
1422
+ let o = await helper.reader.read()
1423
+ controller.enqueue(o.value)
1424
+ },
1425
+ cancel() {
1426
+ helperParam.dstMap.delete(helper.dstId)
1427
+ }
1428
+ })).on('error', (err) => { console.error('web stream error', err.message) })
1429
+ })
1430
+ }
1431
+
1432
+ /**
1433
+ * @param {{
1434
+ * signal:AbortSignal;
1435
+ * serverKey?:string;
1436
+ * url:string;
1437
+ * timeout?:number;
1438
+ * oncreateoutconnect?:()=>{};
1439
+ * headersFn?:()=>Promise<object>;
1440
+ * }} param
1441
+ */
1442
+ export function createTunnelTcpClientHttp(param) {
1443
+ let helper = createTunnelTcpClientHelper({
1444
+ serverKey: param.serverKey,
1445
+ signal: param.signal,
1446
+ uniqueId: 1,
1447
+ clientDataId: 0,
1448
+ })
1449
+ helper.writer = helper.writable.getWriter()
1450
+ let signal = Promise_withResolvers()
1451
+ /** @type{WritableStreamDefaultWriter<Uint8Array>} */
1452
+ let socketWriter = null
1453
+ let bufferedTransform = createTimeBufferedTransformStream(50)
1454
+ helper.readable.pipeThrough(bufferedTransform).pipeTo(new WritableStream({
1455
+ async write(chunk) {
1456
+ while (!param.signal.aborted && socketWriter == null) {
1457
+ await signal.promise
1458
+ }
1459
+ if (!param.signal.aborted) {
1460
+ await socketWriter.write(chunk)
1461
+ }
1462
+ }
1463
+ })).catch((err) => { console.error('web stream error', err.message) })
1464
+
1465
+ /** @type{Set<()=>void>} */
1466
+ const abortListenerSet = new Set()
1467
+ param.signal.addEventListener('abort', () => {
1468
+ abortListenerSet.forEach(o => o())
1469
+ })
1470
+ async function createConnectionIn() {
1471
+ console.info('createTunnelTcpClientHttp createConnectionIn')
1472
+ let addHeaders = {}
1473
+ if (param.headersFn) {
1474
+ addHeaders = await param.headersFn()
1475
+ }
1476
+ const ac = new AbortController()
1477
+ const listenerAC = () => { ac.abort() }
1478
+ try {
1479
+ let transform = new TransformStream()
1480
+ socketWriter = transform.writable.getWriter()
1481
+ signal.resolve()
1482
+ abortListenerSet.add(listenerAC)
1483
+ await fetch(param.url, {
1484
+ method: 'POST',
1485
+ // @ts-ignore
1486
+ duplex: 'half',
1487
+ signal: ac.signal,
1488
+ headers: {
1489
+ 'Content-Type': 'application/octet-stream',
1490
+ ...addHeaders,
1491
+ },
1492
+ body: transform.readable,
1493
+ })
1494
+ } finally {
1495
+ abortListenerSet.delete(listenerAC)
1496
+ socketWriter = null
1497
+ signal.resolve()
1498
+ signal = Promise_withResolvers()
1499
+ }
1500
+ }
1501
+
1502
+ async function createConnectionOut() {
1503
+ console.info('createTunnelTcpClientHttp createConnectionOut')
1504
+ let addHeaders = {}
1505
+ if (param.headersFn) {
1506
+ addHeaders = await param.headersFn()
1507
+ }
1508
+ const ac = new AbortController()
1509
+ const listenerAC = () => { ac.abort() }
1510
+ try {
1511
+ abortListenerSet.add(listenerAC)
1512
+ let res = await fetch(param.url, {
1513
+ method: 'POST',
1514
+ signal: ac.signal,
1515
+ headers: {
1516
+ 'Content-Type': 'application/octet-stream',
1517
+ ...addHeaders,
1518
+ },
1519
+ })
1520
+ param.oncreateoutconnect && param.oncreateoutconnect()
1521
+ await res.body.pipeTo(new WritableStream({
1522
+ async write(chunk) {
1523
+ await helper.writer.write(chunk)
1524
+ }
1525
+ }))
1526
+ } finally {
1527
+ abortListenerSet.delete(listenerAC)
1528
+ }
1529
+ }
1530
+
1531
+ timeWaitRetryLoop(param.signal, async () => {
1532
+ await createConnectionIn()
1533
+ })
1534
+ timeWaitRetryLoop(param.signal, async () => {
1535
+ await createConnectionOut()
1536
+ })
1537
+
1538
+ return helper
1539
+ }