socket-function 0.105.0 → 0.107.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/package.json +1 -1
- package/require/require.ts +18 -10
- package/src/misc.ts +4 -2
- package/src/webSocketServer.ts +362 -356
package/package.json
CHANGED
package/require/require.ts
CHANGED
|
@@ -171,7 +171,7 @@ export function requireMain() {
|
|
|
171
171
|
}} */
|
|
172
172
|
let serializedModules: { [id: string]: SerializedModule | undefined } = Object.create(null);
|
|
173
173
|
|
|
174
|
-
type ModuleType = {
|
|
174
|
+
type ModuleType = NodeJS.Module & {
|
|
175
175
|
id: string;
|
|
176
176
|
filename: string;
|
|
177
177
|
exports: unknown;
|
|
@@ -181,10 +181,12 @@ export function requireMain() {
|
|
|
181
181
|
loaded: boolean;
|
|
182
182
|
isPreloading: boolean;
|
|
183
183
|
evalStartTime: number;
|
|
184
|
-
evalEndTime: number;
|
|
184
|
+
evalEndTime: number | undefined;
|
|
185
|
+
evaluateStarted: boolean;
|
|
185
186
|
source: string;
|
|
186
187
|
allowclient: boolean;
|
|
187
188
|
size: number;
|
|
189
|
+
import: (request: string, asyncIsFine?: boolean) => unknown;
|
|
188
190
|
};
|
|
189
191
|
|
|
190
192
|
let moduleCache: { [id: string]: ModuleType } = Object.create(null);
|
|
@@ -660,9 +662,9 @@ export function requireMain() {
|
|
|
660
662
|
|
|
661
663
|
let currentModuleEvaluationStack: string[] = [];
|
|
662
664
|
// See https://nodejs.org/api/modules.html
|
|
663
|
-
function getModule(resolvedId: string, preload?: "preload") {
|
|
665
|
+
function getModule(resolvedId: string, preload?: "preload"): ModuleType {
|
|
664
666
|
if (resolvedId === "") {
|
|
665
|
-
return {};
|
|
667
|
+
return {} as ModuleType;
|
|
666
668
|
}
|
|
667
669
|
if (resolvedId in moduleCache) {
|
|
668
670
|
let module = moduleCache[resolvedId];
|
|
@@ -685,16 +687,17 @@ export function requireMain() {
|
|
|
685
687
|
console.warn(`Failed to find module ${resolvedId}. The server should have given an error about this.`, serializedModules);
|
|
686
688
|
}
|
|
687
689
|
|
|
688
|
-
let module = Object.create(null);
|
|
690
|
+
let module = Object.create(null) as ModuleType;
|
|
689
691
|
moduleCache[resolvedId] = module;
|
|
690
692
|
module.id = resolvedId;
|
|
691
|
-
module.filename = serializedModule?.filename;
|
|
693
|
+
module.filename = serializedModule?.filename || "";
|
|
692
694
|
module.exports = {};
|
|
693
|
-
|
|
695
|
+
// Default default of exports to the exports itself
|
|
696
|
+
(module.exports as any).default = module.exports;
|
|
694
697
|
module.children = [];
|
|
695
698
|
for (let key in serializedModule?.flags || {}) {
|
|
696
699
|
if (key === "loaded") continue;
|
|
697
|
-
module[key] = true;
|
|
700
|
+
(module as any)[key] = true;
|
|
698
701
|
}
|
|
699
702
|
|
|
700
703
|
module.load = load;
|
|
@@ -717,10 +720,11 @@ export function requireMain() {
|
|
|
717
720
|
const serializedModule = serializedModules[resolvedId];
|
|
718
721
|
if (!serializedModule) return;
|
|
719
722
|
if (!module.loaded) {
|
|
723
|
+
module.evaluateStarted = false;
|
|
720
724
|
if (alreadyHave) {
|
|
721
725
|
delete alreadyHave.seqNums[serializedModule.seqNum];
|
|
722
726
|
}
|
|
723
|
-
// NOTE: There is almost never
|
|
727
|
+
// NOTE: There is almost never a way to recover from module downloading errors, so just don't catch them
|
|
724
728
|
return Promise.resolve()
|
|
725
729
|
.then(() => rootRequire(resolvedId, true))
|
|
726
730
|
.then(async () => {
|
|
@@ -729,8 +733,11 @@ export function requireMain() {
|
|
|
729
733
|
});
|
|
730
734
|
}
|
|
731
735
|
|
|
736
|
+
// Skip double loads
|
|
737
|
+
if (module.evaluateStarted) return;
|
|
738
|
+
|
|
732
739
|
module.requires = serializedModule.requests;
|
|
733
|
-
module.require = createRequire(module, serializedModule);
|
|
740
|
+
module.require = createRequire(module, serializedModule) as any;
|
|
734
741
|
// TODO: Once typescript supports dynamic import, map import() to importDynamic, so it
|
|
735
742
|
// uses our import function, instead of the built in one.
|
|
736
743
|
// (As apparently we can't just override import on a per module basis, because
|
|
@@ -763,6 +770,7 @@ export function requireMain() {
|
|
|
763
770
|
let time = Date.now();
|
|
764
771
|
currentModuleEvaluationStack.push(module.filename);
|
|
765
772
|
try {
|
|
773
|
+
module.evaluateStarted = true;
|
|
766
774
|
module.isPreloading = true;
|
|
767
775
|
module.evalStartTime = nextTime();
|
|
768
776
|
module.evalEndTime = undefined;
|
package/src/misc.ts
CHANGED
|
@@ -446,9 +446,11 @@ export function timeoutToUndefinedSilent<T>(time: number, p: Promise<T>) {
|
|
|
446
446
|
});
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
-
export function errorToWarning<T>(promise: Promise<T>):
|
|
449
|
+
export function errorToWarning<T>(promise: Promise<T>): void {
|
|
450
|
+
// Return the promise, so they can wait if they want, but have the return type be void,
|
|
451
|
+
// so that they don't have to await it.
|
|
450
452
|
return promise.catch(e => {
|
|
451
453
|
console.warn(e.stack);
|
|
452
454
|
return undefined as any;
|
|
453
|
-
});
|
|
455
|
+
}) as any;
|
|
454
456
|
}
|
package/src/webSocketServer.ts
CHANGED
|
@@ -1,356 +1,362 @@
|
|
|
1
|
-
import https from "https";
|
|
2
|
-
import http from "http";
|
|
3
|
-
import net from "net";
|
|
4
|
-
import tls from "tls";
|
|
5
|
-
import * as ws from "ws";
|
|
6
|
-
import { getNodeIdsFromRequest, httpCallHandler } from "./callHTTPHandler";
|
|
7
|
-
import { SocketFunction } from "../SocketFunction";
|
|
8
|
-
import { getTrustedCertificates, watchTrustedCertificates } from "./certStore";
|
|
9
|
-
import { createCallFactory } from "./CallFactory";
|
|
10
|
-
import { parseSNIExtension, parseTLSHello, SNIType } from "./tlsParsing";
|
|
11
|
-
import debugbreak from "debugbreak";
|
|
12
|
-
import { getNodeId } from "./nodeCache";
|
|
13
|
-
import crypto from "crypto";
|
|
14
|
-
import { Watchable, getRootDomain, timeInHour, timeInMinute } from "./misc";
|
|
15
|
-
import { delay, runInfinitePoll, runInfinitePollCallAtStart } from "./batching";
|
|
16
|
-
import { magenta, red } from "./formatting/logColors";
|
|
17
|
-
import { yellow } from "./formatting/logColors";
|
|
18
|
-
import { green } from "./formatting/logColors";
|
|
19
|
-
import { formatTime } from "./formatting/format";
|
|
20
|
-
import { getExternalIP, testTCPIsListening } from "./networking";
|
|
21
|
-
import { forwardPort } from "./forwardPort";
|
|
22
|
-
|
|
23
|
-
export type SocketServerConfig = (
|
|
24
|
-
https.ServerOptions & {
|
|
25
|
-
key: string | Buffer;
|
|
26
|
-
cert: string | Buffer;
|
|
27
|
-
|
|
28
|
-
port: number;
|
|
29
|
-
/** You can also set `port: 0` if you don't care what port you want at all. */
|
|
30
|
-
useAvailablePortIfPortInUse?: boolean;
|
|
31
|
-
|
|
32
|
-
// public sets ip to "0.0.0.0", otherwise it defaults to "127.0.0.1", which
|
|
33
|
-
// causes the server to only accept local connections.
|
|
34
|
-
public?: boolean;
|
|
35
|
-
/** Tries forwarding ports (using UPnP), if we detect they aren't externally reachable.
|
|
36
|
-
* - This causes an extra request and delay during startup, so should only be used
|
|
37
|
-
* during development.
|
|
38
|
-
* - Ignored if public is false (in which case we mount on 127.0.0.1, so port forwarding
|
|
39
|
-
* wouldn't matter anyways).
|
|
40
|
-
*/
|
|
41
|
-
autoForwardPort?: boolean;
|
|
42
|
-
ip?: string;
|
|
43
|
-
|
|
44
|
-
// NOTE: Any same origin accesses are allowed (header.origin === header.host)
|
|
45
|
-
// For example, to allow "letx.ca" to access the server (when the hosted domain
|
|
46
|
-
// may be, "querysub.com", for example), use ["letx.ca"]
|
|
47
|
-
allowHostnames?: string[];
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
let
|
|
70
|
-
let
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
httpsServerLast.
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
let
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
httpsServer.on("
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
// NOTE:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
realServer.
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
1
|
+
import https from "https";
|
|
2
|
+
import http from "http";
|
|
3
|
+
import net from "net";
|
|
4
|
+
import tls from "tls";
|
|
5
|
+
import * as ws from "ws";
|
|
6
|
+
import { getNodeIdsFromRequest, httpCallHandler } from "./callHTTPHandler";
|
|
7
|
+
import { SocketFunction } from "../SocketFunction";
|
|
8
|
+
import { getTrustedCertificates, watchTrustedCertificates } from "./certStore";
|
|
9
|
+
import { createCallFactory } from "./CallFactory";
|
|
10
|
+
import { parseSNIExtension, parseTLSHello, SNIType } from "./tlsParsing";
|
|
11
|
+
import debugbreak from "debugbreak";
|
|
12
|
+
import { getNodeId } from "./nodeCache";
|
|
13
|
+
import crypto from "crypto";
|
|
14
|
+
import { Watchable, getRootDomain, timeInHour, timeInMinute } from "./misc";
|
|
15
|
+
import { delay, runInfinitePoll, runInfinitePollCallAtStart } from "./batching";
|
|
16
|
+
import { magenta, red } from "./formatting/logColors";
|
|
17
|
+
import { yellow } from "./formatting/logColors";
|
|
18
|
+
import { green } from "./formatting/logColors";
|
|
19
|
+
import { formatTime } from "./formatting/format";
|
|
20
|
+
import { getExternalIP, testTCPIsListening } from "./networking";
|
|
21
|
+
import { forwardPort } from "./forwardPort";
|
|
22
|
+
|
|
23
|
+
export type SocketServerConfig = (
|
|
24
|
+
https.ServerOptions & {
|
|
25
|
+
key: string | Buffer;
|
|
26
|
+
cert: string | Buffer;
|
|
27
|
+
|
|
28
|
+
port: number;
|
|
29
|
+
/** You can also set `port: 0` if you don't care what port you want at all. */
|
|
30
|
+
useAvailablePortIfPortInUse?: boolean;
|
|
31
|
+
|
|
32
|
+
// public sets ip to "0.0.0.0", otherwise it defaults to "127.0.0.1", which
|
|
33
|
+
// causes the server to only accept local connections.
|
|
34
|
+
public?: boolean;
|
|
35
|
+
/** Tries forwarding ports (using UPnP), if we detect they aren't externally reachable.
|
|
36
|
+
* - This causes an extra request and delay during startup, so should only be used
|
|
37
|
+
* during development.
|
|
38
|
+
* - Ignored if public is false (in which case we mount on 127.0.0.1, so port forwarding
|
|
39
|
+
* wouldn't matter anyways).
|
|
40
|
+
*/
|
|
41
|
+
autoForwardPort?: boolean;
|
|
42
|
+
ip?: string;
|
|
43
|
+
|
|
44
|
+
// NOTE: Any same origin accesses are allowed (header.origin === header.host)
|
|
45
|
+
// For example, to allow "letx.ca" to access the server (when the hosted domain
|
|
46
|
+
// may be, "querysub.com", for example), use ["letx.ca"]
|
|
47
|
+
allowHostnames?: string[];
|
|
48
|
+
// If a hostname is in allowHostnames or allowHostnameFnc returns true, it is allowed
|
|
49
|
+
allowHostnameFnc?: (hostname: string) => boolean;
|
|
50
|
+
|
|
51
|
+
/** If the SNI matches this domain, we use a different key/cert.
|
|
52
|
+
* We remove subdomains until we find a match
|
|
53
|
+
*/
|
|
54
|
+
SNICerts?: {
|
|
55
|
+
[domain: string]: Watchable<https.ServerOptions>;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export async function startSocketServer(
|
|
61
|
+
config: SocketServerConfig
|
|
62
|
+
): Promise<string> {
|
|
63
|
+
|
|
64
|
+
const webSocketServer = new ws.Server({
|
|
65
|
+
noServer: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
async function setupHTTPSServer(watchOptions: Watchable<https.ServerOptions>) {
|
|
69
|
+
let httpsServerLast: https.Server | undefined;
|
|
70
|
+
let onHttpServer: (server: https.Server) => void;
|
|
71
|
+
let httpServerPromise = new Promise<https.Server>(r => onHttpServer = r);
|
|
72
|
+
let lastOptions!: https.ServerOptions;
|
|
73
|
+
await watchOptions(value => {
|
|
74
|
+
// NOTE: If this is called a lot... STOP CALLING IT A LOT! Calling setSecureContext
|
|
75
|
+
// so frequently likely leaks memory!
|
|
76
|
+
console.log(`Updating websocket server options`);
|
|
77
|
+
lastOptions = {
|
|
78
|
+
...value,
|
|
79
|
+
ca: getTrustedCertificates(),
|
|
80
|
+
// Attempt to disable sessions, because they make SNI significantly harder to parse.
|
|
81
|
+
secureOptions: require("node:constants").SSL_OP_NO_TICKET,
|
|
82
|
+
};
|
|
83
|
+
if (!httpsServerLast) {
|
|
84
|
+
httpsServerLast = https.createServer(lastOptions);
|
|
85
|
+
// NOTE: This MIGHT be different than the keep alive option? Probably not, but also...
|
|
86
|
+
// something weird is happening with connections...
|
|
87
|
+
httpsServerLast.keepAliveTimeout = 0;
|
|
88
|
+
} else {
|
|
89
|
+
httpsServerLast.setSecureContext(lastOptions);
|
|
90
|
+
}
|
|
91
|
+
onHttpServer(httpsServerLast);
|
|
92
|
+
});
|
|
93
|
+
let httpsServer = await httpServerPromise;
|
|
94
|
+
|
|
95
|
+
let allowedHostnames = new Set<string>();
|
|
96
|
+
for (let hostname of config.allowHostnames || []) {
|
|
97
|
+
allowedHostnames.add(hostname);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
watchTrustedCertificates(() => {
|
|
101
|
+
// NOTE: If this is called a lot... STOP CALLING IT A LOT! Calling setSecureContext
|
|
102
|
+
// so frequently likely leaks memory!
|
|
103
|
+
console.log(`Updating websocket server trusted certificates`);
|
|
104
|
+
lastOptions.ca = getTrustedCertificates();
|
|
105
|
+
httpsServer.setSecureContext(lastOptions);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
httpsServer.on("connection", socket => {
|
|
109
|
+
let debug = (socket as any).remoteAddress + ":" + (socket as any).remotePort;
|
|
110
|
+
if (!SocketFunction.silent) {
|
|
111
|
+
console.log(`HTTP server connection established ${debug}`);
|
|
112
|
+
}
|
|
113
|
+
socket.on("error", e => {
|
|
114
|
+
if (!SocketFunction.silent) {
|
|
115
|
+
console.log(`HTTP server socket error for ${debug}, ${e.message}`);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
socket.on("close", () => {
|
|
119
|
+
if (!SocketFunction.silent) {
|
|
120
|
+
console.log(`HTTP server socket closed for ${debug}`);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
httpsServer.on("error", e => {
|
|
125
|
+
console.error(`Connection attempt error ${e.message}`);
|
|
126
|
+
});
|
|
127
|
+
httpsServer.on("tlsClientError", e => {
|
|
128
|
+
// NOTE: This happens a lot when we have tabs open that connected to an old
|
|
129
|
+
// server (with old certs, that the browser will reject?)
|
|
130
|
+
if (!SocketFunction.silent) {
|
|
131
|
+
console.error(`TLS client error ${e.message}`);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
httpsServer.on("request", httpCallHandler);
|
|
136
|
+
|
|
137
|
+
httpsServer.on("upgrade", (request, socket, upgradeHead) => {
|
|
138
|
+
let originHeader = request.headers["origin"];
|
|
139
|
+
if (originHeader) {
|
|
140
|
+
try {
|
|
141
|
+
let host = getRootDomain(new URL("ws://" + request.headers["host"]).hostname);
|
|
142
|
+
let origin = getRootDomain(new URL(originHeader).hostname);
|
|
143
|
+
let allowed = host === origin || allowedHostnames.has(origin);
|
|
144
|
+
if (!allowed && config.allowHostnameFnc) {
|
|
145
|
+
allowed = config.allowHostnameFnc(origin);
|
|
146
|
+
}
|
|
147
|
+
if (!allowed) {
|
|
148
|
+
throw new Error(`Invalid cross domain request, ${JSON.stringify(host)} !== ${JSON.stringify(origin)} (also not in config.allowedHostnames ${JSON.stringify(config.allowHostnames)})`);
|
|
149
|
+
}
|
|
150
|
+
} catch (e) {
|
|
151
|
+
// Destroy the socket, so we don't lock up the client
|
|
152
|
+
socket.destroy();
|
|
153
|
+
// NOTE: Just log, because invalid requests are guaranteed to happen, and
|
|
154
|
+
// there's no point wasting time looking at them.
|
|
155
|
+
console.log(e);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
webSocketServer.handleUpgrade(request, socket, upgradeHead, (ws) => {
|
|
160
|
+
const { nodeId, localNodeId } = getNodeIdsFromRequest(request);
|
|
161
|
+
createCallFactory(ws, nodeId, localNodeId).catch(e => {
|
|
162
|
+
console.error(`Error in creating call factory, ${e.stack}`);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
return httpsServer;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// TODO: Only allow unauthorized for ip certificates, and then for domains use the domain as the nodeId,
|
|
170
|
+
// so it is easy to read, and consistent.
|
|
171
|
+
let options: https.ServerOptions = {
|
|
172
|
+
...config,
|
|
173
|
+
// Keep alive causes problems with our HTTP requests. AND... almost all of our data uses
|
|
174
|
+
// our websockets, so... we really don't need to keep alive our HTTP requests
|
|
175
|
+
// (and our images go through cloudflare, so we don't even need keep alive for that)
|
|
176
|
+
keepAlive: false,
|
|
177
|
+
keepAliveInitialDelay: 0,
|
|
178
|
+
noDelay: true,
|
|
179
|
+
};
|
|
180
|
+
if (!config.cert) {
|
|
181
|
+
throw new Error("No cert specified");
|
|
182
|
+
}
|
|
183
|
+
if (!config.key) {
|
|
184
|
+
throw new Error("No key specified");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const mainHTTPSServer = await setupHTTPSServer(callback => callback(options));
|
|
188
|
+
let sniServers = new Map<string, https.Server>();
|
|
189
|
+
for (let [domain, obj] of Object.entries(config.SNICerts || {})) {
|
|
190
|
+
sniServers.set(domain, await setupHTTPSServer(obj));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let httpServer = http.createServer({ keepAlive: false, }, async function (req, res) {
|
|
194
|
+
let url = new URL("http://" + req.headers.host + req.url);
|
|
195
|
+
url.protocol = "https:";
|
|
196
|
+
//url.hostname = opts.hostname;
|
|
197
|
+
url.hostname = req.headers.host || "";
|
|
198
|
+
res.writeHead(301, { Location: url + "" });
|
|
199
|
+
res.end();
|
|
200
|
+
});
|
|
201
|
+
httpServer.keepAliveTimeout = 0;
|
|
202
|
+
httpServer.on("error", e => {
|
|
203
|
+
console.error(`HTTP error ${e.stack}`);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
let realServer = net.createServer(socket => {
|
|
207
|
+
const debug = socket.remoteAddress + ":" + socket.remotePort;
|
|
208
|
+
if (!SocketFunction.silent) {
|
|
209
|
+
console.log(`Received TCP connection from ${debug}`);
|
|
210
|
+
}
|
|
211
|
+
function handleTLSHello(buffer: Buffer, packetCount: number): void | "more" {
|
|
212
|
+
if (!SocketFunction.silent) {
|
|
213
|
+
console.log(`Received TCP header packet from ${debug}, have ${buffer.length} bytes so far, ${packetCount} packets`);
|
|
214
|
+
}
|
|
215
|
+
// All HTTPS requests start with 22, and no HTTP requests start with 22,
|
|
216
|
+
// so we just need to read the first byte.
|
|
217
|
+
let server: https.Server | http.Server;
|
|
218
|
+
if (buffer[0] !== 22) {
|
|
219
|
+
server = httpServer;
|
|
220
|
+
} else {
|
|
221
|
+
let data = parseTLSHello(buffer);
|
|
222
|
+
if (data.missingBytes > 0) {
|
|
223
|
+
return "more";
|
|
224
|
+
}
|
|
225
|
+
let sni = data.extensions.filter(x => x.type === SNIType).flatMap(x => parseSNIExtension(x.data))[0];
|
|
226
|
+
if (!SocketFunction.silent) {
|
|
227
|
+
console.log(`Received TCP connection with SNI ${JSON.stringify(sni)}. Have handlers for: ${Array.from(sniServers.keys()).join(", ")}`);
|
|
228
|
+
}
|
|
229
|
+
if (!sni) {
|
|
230
|
+
console.warn(`No SNI found in TLS hello from ${debug}, using main server. Packets ${packetCount}`);
|
|
231
|
+
console.log(buffer.toString("base64"));
|
|
232
|
+
}
|
|
233
|
+
let originalSNI = sni;
|
|
234
|
+
if (sni) {
|
|
235
|
+
// Remove subdomains until we can find a domain
|
|
236
|
+
while (!sniServers.has(sni)) {
|
|
237
|
+
let parts = sni.split(".");
|
|
238
|
+
if (parts.length <= 2) break;
|
|
239
|
+
sni = parts.slice(1).join(".");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!sniServers.has(sni)) {
|
|
244
|
+
console.warn(`No SNI server found for ${originalSNI}, using main server. SNI candidates ${Array.from(sniServers.keys()).join(", ")}`);
|
|
245
|
+
}
|
|
246
|
+
server = sniServers.get(sni) || mainHTTPSServer;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// NOTE: Messages aren't dequeued until the current handler finishes, so we don't need to pause the socket or anything.
|
|
250
|
+
server.emit("connection", socket);
|
|
251
|
+
socket.unshift(buffer);
|
|
252
|
+
}
|
|
253
|
+
let buffers: Buffer[] = [];
|
|
254
|
+
function getNextData() {
|
|
255
|
+
// NOTE: ONCE is used, so we only look at the first buffer, and then after that
|
|
256
|
+
// we pipe. This should be very efficient, as pipe has insane throughput
|
|
257
|
+
// (100s of MB/s, easily, even on a terrible machine).
|
|
258
|
+
socket.once("data", buffer => {
|
|
259
|
+
buffers.push(buffer);
|
|
260
|
+
let result = handleTLSHello(Buffer.concat(buffers), buffers.length);
|
|
261
|
+
if (result === "more") {
|
|
262
|
+
getNextData();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
getNextData();
|
|
267
|
+
socket.on("error", (e) => {
|
|
268
|
+
console.error(`TCP socket error for ${debug}, ${e.stack}`);
|
|
269
|
+
});
|
|
270
|
+
socket.on("close", () => {
|
|
271
|
+
if (!SocketFunction.silent) {
|
|
272
|
+
console.log(`TCP socket closed for ${debug}`);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
let host = config.public ? "0.0.0.0" : "127.0.0.1";
|
|
279
|
+
if (config.ip) {
|
|
280
|
+
host = config.ip;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let port = config.port;
|
|
284
|
+
if (!SocketFunction.silent) {
|
|
285
|
+
console.log(yellow(`Trying to listening on ${host}:${port}`));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let listeningPromise = waitUntilListening();
|
|
289
|
+
listeningPromise.catch(e => { });
|
|
290
|
+
|
|
291
|
+
// Return true if we are listening, false if the address is in use, and throws on other errors
|
|
292
|
+
async function waitUntilListening() {
|
|
293
|
+
return await new Promise<boolean>((resolve, reject) => {
|
|
294
|
+
realServer.once("error", e => {
|
|
295
|
+
reject(e);
|
|
296
|
+
});
|
|
297
|
+
realServer.once("listening", () => {
|
|
298
|
+
resolve(false);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (config.useAvailablePortIfPortInUse && port) {
|
|
304
|
+
realServer.listen(port, host);
|
|
305
|
+
let isListening = await new Promise<boolean>((resolve, reject) => {
|
|
306
|
+
if (realServer.listening) {
|
|
307
|
+
resolve(true);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
realServer.once("error", e => {
|
|
311
|
+
if (e.message.includes("EADDRINUSE")) {
|
|
312
|
+
resolve(true);
|
|
313
|
+
} else {
|
|
314
|
+
reject(e);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
realServer.once("listening", () => {
|
|
318
|
+
resolve(false);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
if (!isListening) {
|
|
322
|
+
port = 0;
|
|
323
|
+
realServer.listen(port, host);
|
|
324
|
+
listeningPromise = waitUntilListening();
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
realServer.listen(port, host);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
await listeningPromise;
|
|
331
|
+
port = (realServer.address() as net.AddressInfo).port;
|
|
332
|
+
|
|
333
|
+
if (config.autoForwardPort && config.public) {
|
|
334
|
+
// let externalIP = await getExternalIP();
|
|
335
|
+
// let isListening = await testTCPIsListening(externalIP, port);
|
|
336
|
+
// if (!isListening) {
|
|
337
|
+
// console.log(magenta(`Port ${port} is not externally reachable, trying to forward it`));
|
|
338
|
+
// await forwardPort({ externalPort: port, internalPort: port });
|
|
339
|
+
// }
|
|
340
|
+
// Even if they are listening, they might not stay listening. Forward every 8 hours
|
|
341
|
+
// (including at the start, in case the forward is about to expire).
|
|
342
|
+
async function forward() {
|
|
343
|
+
await forwardPort({ externalPort: port, internalPort: port });
|
|
344
|
+
console.log(magenta(`Forwarded port ${port} to our machine`));
|
|
345
|
+
}
|
|
346
|
+
// Every hour, in case our network configuration changes
|
|
347
|
+
runInfinitePollCallAtStart(timeInMinute * 30, forward).catch(e => console.error(red(`Error in port forwarding ${e.stack}`)));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let nodeId = getNodeId(getCommonName(config.cert), port);
|
|
351
|
+
console.log(green(`Started Listening on ${nodeId} (${host}) after ${formatTime(process.uptime() * 1000)}`));
|
|
352
|
+
|
|
353
|
+
return nodeId;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function getCommonName(cert: Buffer | string) {
|
|
357
|
+
let subject = new crypto.X509Certificate(cert).subject;
|
|
358
|
+
let subjectKVPs = new Map(subject.split(",").map(x => x.trim().split("=")).map(x => [x[0], x.slice(1).join("=")]));
|
|
359
|
+
let commonName = subjectKVPs.get("CN");
|
|
360
|
+
if (!commonName) throw new Error(`No common name in subject: ${subject}`);
|
|
361
|
+
return commonName;
|
|
362
|
+
}
|