socket-function 0.12.16 → 0.14.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/SocketFunction.ts +15 -2
- package/SocketFunctionTypes.ts +13 -6
- package/package.json +2 -2
- package/src/CallFactory.ts +119 -31
- package/src/JSONLACKS/JSONLACKS.generated.js +302 -72
- package/src/JSONLACKS/JSONLACKS.pegjs +31 -6
- package/src/JSONLACKS/JSONLACKS.ts +17 -2
- package/src/batching.ts +50 -10
- package/src/caching.ts +6 -4
- package/src/callManager.ts +21 -3
- package/src/formatting/format.ts +8 -0
- package/src/misc.ts +10 -0
- package/src/nodeCache.ts +1 -0
- package/src/profiling/measure.ts +28 -8
- package/src/tlsParsing.ts +5 -1
- package/src/webSocketServer.ts +26 -8
- package/src/websocketFactory.ts +57 -51
- package/time/trueTimeShim.ts +8 -0
package/src/batching.ts
CHANGED
|
@@ -11,8 +11,19 @@ import { MaybePromise } from "./types";
|
|
|
11
11
|
- The ensures a prompt return, without resorting to setTimeout in the browser (which will cause
|
|
12
12
|
the callback to be delayed a frame).
|
|
13
13
|
*/
|
|
14
|
-
export type DelayType =
|
|
15
|
-
|
|
14
|
+
export type DelayType = (
|
|
15
|
+
number | "afterio" | "immediate" | "afterpromises"
|
|
16
|
+
// Waits for paint, usable in a loop. The first wait doesn't wait until the next
|
|
17
|
+
// wait, but the second wait will.
|
|
18
|
+
| "paintLoop"
|
|
19
|
+
// Waits until after paint, by waiting twice.
|
|
20
|
+
| "afterPaint"
|
|
21
|
+
)
|
|
22
|
+
export function delay(
|
|
23
|
+
delayTime: DelayType,
|
|
24
|
+
// Delays < 10ms become "immediate"
|
|
25
|
+
immediateShortDelays?: "immediateShortDelays"
|
|
26
|
+
): Promise<void> {
|
|
16
27
|
if (delayTime === "afterio") {
|
|
17
28
|
return new Promise<void>(resolve => setImmediate(resolve));
|
|
18
29
|
} else if (delayTime === "afterpromises") {
|
|
@@ -25,16 +36,45 @@ export function delay(delayTime: DelayType): Promise<void> {
|
|
|
25
36
|
} else {
|
|
26
37
|
return delay("afterpromises");
|
|
27
38
|
}
|
|
39
|
+
} else if (delayTime === "paintLoop") {
|
|
40
|
+
if (isNode()) {
|
|
41
|
+
return delay("immediate");
|
|
42
|
+
}
|
|
43
|
+
return (async () => {
|
|
44
|
+
await new Promise(resolve => requestAnimationFrame(resolve));
|
|
45
|
+
})();
|
|
46
|
+
} else if (delayTime === "afterPaint") {
|
|
47
|
+
if (isNode()) {
|
|
48
|
+
return delay("immediate");
|
|
49
|
+
} else {
|
|
50
|
+
return (async () => {
|
|
51
|
+
await new Promise(resolve => requestAnimationFrame(resolve));
|
|
52
|
+
// Before first paint
|
|
53
|
+
await new Promise(resolve => requestAnimationFrame(resolve));
|
|
54
|
+
// After first paint
|
|
55
|
+
})();
|
|
56
|
+
}
|
|
28
57
|
} else {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
58
|
+
|
|
59
|
+
if (delayTime < 10 && immediateShortDelays) {
|
|
60
|
+
// NOTE: setTimeout can't wait this short of a time, so just setImmediate. This should be hard to distinguish
|
|
61
|
+
// anyways, as setImmediate (at least in nodejs), should happen after io, so... it should just work
|
|
62
|
+
// (the only difference is there will be less unnecessary delay).
|
|
63
|
+
// NOTE: THIS DOES break certain cases where io is depending on true delay, and by only waiting a microtick
|
|
64
|
+
// we don't give it a chance. But... we should just handle those cases explicitly, via an explicit "afterio".
|
|
35
65
|
return delay("immediate");
|
|
36
66
|
}
|
|
37
|
-
|
|
67
|
+
// NOTE: We check Date.now() and wait longer if setTimeout didn't wait long enough.
|
|
68
|
+
return (async () => {
|
|
69
|
+
let targetTime = Date.now() + delayTime;
|
|
70
|
+
while (true) {
|
|
71
|
+
let timeToWait = targetTime - Date.now();
|
|
72
|
+
await new Promise<void>(resolve => setTimeout(resolve, timeToWait));
|
|
73
|
+
if (Date.now() >= targetTime) {
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
38
78
|
}
|
|
39
79
|
}
|
|
40
80
|
|
|
@@ -110,7 +150,7 @@ export function batchFunction<Arg, Result = void>(
|
|
|
110
150
|
let args: Arg[] = [arg];
|
|
111
151
|
let promise = Promise.resolve().then(async () => {
|
|
112
152
|
await curPrevPromise;
|
|
113
|
-
await delay(curDelay);
|
|
153
|
+
await delay(curDelay, "immediateShortDelays");
|
|
114
154
|
// Reset batching, as we once we start the function we can't accept args. `prevPromise` will block
|
|
115
155
|
// the next batch from starting before we finish.
|
|
116
156
|
batching = undefined;
|
package/src/caching.ts
CHANGED
|
@@ -37,8 +37,10 @@ export function cacheEmptyArray<T>(array: T[]): T[] {
|
|
|
37
37
|
return array;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export function cache<Output, Key
|
|
41
|
-
(key: Key
|
|
40
|
+
export function cache<Output, Key, Untracked extends unknown[]>(
|
|
41
|
+
getValue: (key: Key, ...untracked: Untracked) => Output
|
|
42
|
+
): {
|
|
43
|
+
(key: Key, ...untracked: Untracked): Output;
|
|
42
44
|
// NOTE: If you want to clear all, just make a new cache!
|
|
43
45
|
clear(key: Key): void;
|
|
44
46
|
clearAll(): void;
|
|
@@ -48,7 +50,7 @@ export function cache<Output, Key>(getValue: (key: Key) => Output): {
|
|
|
48
50
|
} {
|
|
49
51
|
let startingCalculating = new Set<Key>();
|
|
50
52
|
let values = new Map<Key, Output>();
|
|
51
|
-
function cache(input: Key) {
|
|
53
|
+
function cache(input: Key, ...untracked: Untracked) {
|
|
52
54
|
let key = input;
|
|
53
55
|
if (values.has(key)) {
|
|
54
56
|
return values.get(key) as any;
|
|
@@ -59,7 +61,7 @@ export function cache<Output, Key>(getValue: (key: Key) => Output): {
|
|
|
59
61
|
return undefined;
|
|
60
62
|
}
|
|
61
63
|
startingCalculating.add(key);
|
|
62
|
-
let value = getValue(input);
|
|
64
|
+
let value = getValue(input, ...untracked);
|
|
63
65
|
values.set(key, value);
|
|
64
66
|
return value;
|
|
65
67
|
}
|
package/src/callManager.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CallerContext, CallType, ClientHookContext, FullCallType, HookContext, SocketExposedInterface, SocketExposedInterfaceClass, SocketExposedShape, SocketFunctionClientHook, SocketFunctionHook, SocketRegistered } from "../SocketFunctionTypes";
|
|
1
|
+
import { CallerContext, CallType, ClientHookContext, FullCallType, FunctionFlags, HookContext, SocketExposedInterface, SocketExposedInterfaceClass, SocketExposedShape, SocketFunctionClientHook, SocketFunctionHook, SocketRegistered } from "../SocketFunctionTypes";
|
|
2
2
|
import { _setSocketContext } from "../SocketFunction";
|
|
3
|
-
import { isNode } from "./misc";
|
|
3
|
+
import { entries, isNode } from "./misc";
|
|
4
4
|
import debugbreak from "debugbreak";
|
|
5
5
|
import { measureWrap } from "./profiling/measure";
|
|
6
6
|
|
|
@@ -15,6 +15,9 @@ let exposedClasses = new Set<string>();
|
|
|
15
15
|
let globalHooks: SocketFunctionHook[] = [];
|
|
16
16
|
let globalClientHooks: SocketFunctionClientHook[] = [];
|
|
17
17
|
|
|
18
|
+
export function getCallFlags(call: CallType): FunctionFlags | undefined {
|
|
19
|
+
return classes[call.classGuid]?.shape[call.functionName];
|
|
20
|
+
}
|
|
18
21
|
export function shouldCompressCall(call: CallType) {
|
|
19
22
|
return !!classes[call.classGuid]?.shape[call.functionName]?.compress;
|
|
20
23
|
}
|
|
@@ -63,11 +66,26 @@ export function isDataImmutable(call: CallType) {
|
|
|
63
66
|
return !!classes[call.classGuid]?.shape[call.functionName]?.dataImmutable;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
export function registerClass(classGuid: string, controller: SocketExposedInterface, shape: SocketExposedShape
|
|
69
|
+
export function registerClass(classGuid: string, controller: SocketExposedInterface, shape: SocketExposedShape, config?: {
|
|
70
|
+
noFunctionMeasure?: boolean;
|
|
71
|
+
}) {
|
|
67
72
|
if (classes[classGuid]) {
|
|
68
73
|
throw new Error(`Class ${classGuid} already registered`);
|
|
69
74
|
}
|
|
70
75
|
|
|
76
|
+
if (!config?.noFunctionMeasure) {
|
|
77
|
+
let keys = new Set([
|
|
78
|
+
...Object.keys(controller),
|
|
79
|
+
...Object.getOwnPropertyNames(controller.__proto__ || {}),
|
|
80
|
+
]);
|
|
81
|
+
let niceClassName = controller.constructor.name || classGuid;
|
|
82
|
+
for (let functionName of keys) {
|
|
83
|
+
if (functionName === "constructor") continue;
|
|
84
|
+
let fnc = controller[functionName];
|
|
85
|
+
if (typeof fnc !== "function") continue;
|
|
86
|
+
controller[functionName] = measureWrap(fnc, `${niceClassName}().${functionName}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
71
89
|
classes[classGuid] = {
|
|
72
90
|
controller,
|
|
73
91
|
shape,
|
package/src/formatting/format.ts
CHANGED
|
@@ -122,6 +122,7 @@ export function formatMaxDecimals(num: number, targetDigits: number, maxAbsolute
|
|
|
122
122
|
|
|
123
123
|
/** Actually formats any number, including decimals, by using K, M and B suffixes to get smaller values
|
|
124
124
|
* TODO: Support uK, uM and uB suffixes for very small numbers?
|
|
125
|
+
* <= 6 characters (<= 5 if positive)
|
|
125
126
|
*/
|
|
126
127
|
export function formatNumber(count: number | undefined, maxAbsoluteValue?: number, noDecimal?: boolean, specialCurrency?: boolean): string {
|
|
127
128
|
if (typeof count !== "number") return "0";
|
|
@@ -230,4 +231,11 @@ export function formatDate(time: number) {
|
|
|
230
231
|
}
|
|
231
232
|
let date = new Date(time);
|
|
232
233
|
return date.getFullYear() + "/" + p(date.getMonth() + 1) + "/" + p(date.getDate());
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** <= 6 characters (<= 5 if positive) */
|
|
237
|
+
export function formatPercent(value: number) {
|
|
238
|
+
if (Number.isNaN(value)) return "0%";
|
|
239
|
+
// 1 decimal point, so we have 5 characters (just like formatNumber returns 5 characters)
|
|
240
|
+
return Math.round(value * 1000) / 10 + "%";
|
|
233
241
|
}
|
package/src/misc.ts
CHANGED
|
@@ -324,6 +324,16 @@ export function compare(lhs: unknown, rhs: unknown): number {
|
|
|
324
324
|
return compare(typeof lhs, typeof rhs);
|
|
325
325
|
}
|
|
326
326
|
if (lhs === rhs) return 0;
|
|
327
|
+
if (lhs === null && rhs !== null) return -1;
|
|
328
|
+
if (lhs !== null && rhs === null) return 1;
|
|
329
|
+
if (typeof lhs === "number") {
|
|
330
|
+
if (Number.isNaN(lhs)) {
|
|
331
|
+
if (Number.isNaN(rhs)) return 0;
|
|
332
|
+
return -1;
|
|
333
|
+
} else {
|
|
334
|
+
if (Number.isNaN(rhs)) return +1;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
327
337
|
if (lhs as any < (rhs as any)) return -1;
|
|
328
338
|
return 1;
|
|
329
339
|
}
|
package/src/nodeCache.ts
CHANGED
|
@@ -18,6 +18,7 @@ export function getNodeId(domain: string, port: number): string {
|
|
|
18
18
|
return `${domain}:${port}`;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** @deprecated, call getBrowserUrlNode instead, which does important additional checks. */
|
|
21
22
|
export function getNodeIdFromLocation() {
|
|
22
23
|
return SocketFunction.browserNodeId();
|
|
23
24
|
}
|
package/src/profiling/measure.ts
CHANGED
|
@@ -61,7 +61,7 @@ export function measureWrap<T extends (...args: any[]) => any>(fnc: T, name?: st
|
|
|
61
61
|
functionsSkipped++;
|
|
62
62
|
return fnc;
|
|
63
63
|
}
|
|
64
|
-
let usedName = name || fnc.name;
|
|
64
|
+
let usedName = name || fnc.name || fnc.toString().slice(0, 100).replaceAll(/\s/g, " ");
|
|
65
65
|
return nameFunction(usedName, (function (this: any, ...args: unknown[]): unknown {
|
|
66
66
|
if (outstandingProfiles.length === 0) {
|
|
67
67
|
return fnc.apply(this, args);
|
|
@@ -127,17 +127,18 @@ export interface LogMeasureTableConfig {
|
|
|
127
127
|
minTimeToLog?: number;
|
|
128
128
|
// Defaults to 2
|
|
129
129
|
mergeDepth?: number;
|
|
130
|
+
// Defaults to 10
|
|
131
|
+
maxTableEntries?: number;
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
export function logMeasureTable(
|
|
133
135
|
profile: MeasureProfile,
|
|
134
136
|
config?: LogMeasureTableConfig
|
|
135
137
|
) {
|
|
136
|
-
let { useTotalTime, name
|
|
137
|
-
|
|
138
|
-
thresholdInTable = 0.05;
|
|
139
|
-
}
|
|
138
|
+
let { useTotalTime, name } = config || {};
|
|
139
|
+
const thresholdInTable = config?.thresholdInTable ?? 0.05;
|
|
140
140
|
let minTimeToLog = config?.minTimeToLog ?? 50;
|
|
141
|
+
const maxTableEntries = config?.maxTableEntries ?? 10;
|
|
141
142
|
|
|
142
143
|
function getTime(entry: ProfileEntry) {
|
|
143
144
|
return useTotalTime ? entry.totalTime : entry.ownTime;
|
|
@@ -188,11 +189,30 @@ export function logMeasureTable(
|
|
|
188
189
|
return `${(value * 100).toFixed(2)}%`;
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
|
|
192
|
+
let remaining = entries.slice(maxTableEntries);
|
|
193
|
+
entries = entries.slice(0, maxTableEntries);
|
|
194
|
+
entries = entries.filter(entry => {
|
|
195
|
+
const include = getTime(entry).sum / totalTime >= thresholdInTable;
|
|
196
|
+
if (!include) {
|
|
197
|
+
remaining.push(entry);
|
|
198
|
+
}
|
|
199
|
+
return include;
|
|
200
|
+
});
|
|
201
|
+
entries.push({
|
|
202
|
+
name: "Other",
|
|
203
|
+
ownTime: createStatsValue(),
|
|
204
|
+
totalTime: createStatsValue(),
|
|
205
|
+
stillOpenCount: 0,
|
|
206
|
+
});
|
|
207
|
+
let remainingEntry = entries[entries.length - 1];
|
|
208
|
+
for (let entry of remaining) {
|
|
209
|
+
addToStats(remainingEntry.ownTime, entry.ownTime);
|
|
210
|
+
addToStats(remainingEntry.totalTime, entry.totalTime);
|
|
211
|
+
remainingEntry.stillOpenCount += entry.stillOpenCount;
|
|
212
|
+
}
|
|
192
213
|
let maxNameLength = Math.max(...entries.map(x => x.name.length));
|
|
193
214
|
|
|
194
|
-
for (let entry of entries
|
|
195
|
-
if (getTime(entry).sum / totalTime < thresholdInTable) break;
|
|
215
|
+
for (let entry of entries) {
|
|
196
216
|
let output = "";
|
|
197
217
|
output += `${blue(entry.name)}`;
|
|
198
218
|
output += Array(maxNameLength + 4 - entry.name.length).fill(" ").join("");
|
package/src/tlsParsing.ts
CHANGED
|
@@ -3,14 +3,17 @@ export function parseTLSHello(buffer: Buffer): {
|
|
|
3
3
|
type: number;
|
|
4
4
|
data: Buffer;
|
|
5
5
|
}[];
|
|
6
|
+
missingBytes: number;
|
|
6
7
|
} {
|
|
7
8
|
let output: {
|
|
8
9
|
extensions: {
|
|
9
10
|
type: number;
|
|
10
11
|
data: Buffer;
|
|
11
12
|
}[];
|
|
13
|
+
missingBytes: number;
|
|
12
14
|
} = {
|
|
13
|
-
extensions: []
|
|
15
|
+
extensions: [],
|
|
16
|
+
missingBytes: 0
|
|
14
17
|
};
|
|
15
18
|
|
|
16
19
|
try {
|
|
@@ -50,6 +53,7 @@ export function parseTLSHello(buffer: Buffer): {
|
|
|
50
53
|
pos += compressionLength;
|
|
51
54
|
|
|
52
55
|
let extensionsLength = readShort();
|
|
56
|
+
output.missingBytes = contentLength - (pos + extensionsLength);
|
|
53
57
|
let extensionsEnd = pos + extensionsLength;
|
|
54
58
|
while (pos < extensionsEnd) {
|
|
55
59
|
let extensionType = readShort();
|
package/src/webSocketServer.ts
CHANGED
|
@@ -58,7 +58,12 @@ export async function startSocketServer(
|
|
|
58
58
|
let httpServerPromise = new Promise<https.Server>(r => onHttpServer = r);
|
|
59
59
|
let lastOptions!: https.ServerOptions;
|
|
60
60
|
await watchOptions(value => {
|
|
61
|
-
lastOptions = {
|
|
61
|
+
lastOptions = {
|
|
62
|
+
...value,
|
|
63
|
+
ca: getTrustedCertificates(),
|
|
64
|
+
// Attempt to disable sessions, because they make SNI significantly harder to parse.
|
|
65
|
+
secureOptions: require("node:constants").SSL_OP_NO_TICKET,
|
|
66
|
+
};
|
|
62
67
|
if (!httpsServerLast) {
|
|
63
68
|
httpsServerLast = https.createServer(lastOptions);
|
|
64
69
|
} else {
|
|
@@ -119,7 +124,7 @@ export async function startSocketServer(
|
|
|
119
124
|
let host = new URL("ws://" + request.headers["host"]).hostname;
|
|
120
125
|
let origin = new URL(originHeader).hostname;
|
|
121
126
|
if (host !== origin && !allowedHostnames.has(origin)) {
|
|
122
|
-
throw new Error(`Invalid cross domain request, ${JSON.stringify(host)} !== ${JSON.stringify(origin)} (also not config.allowedHostnames ${JSON.stringify(config.allowHostnames)})`);
|
|
127
|
+
throw new Error(`Invalid cross domain request, ${JSON.stringify(host)} !== ${JSON.stringify(origin)} (also not in config.allowedHostnames ${JSON.stringify(config.allowHostnames)})`);
|
|
123
128
|
}
|
|
124
129
|
} catch (e) {
|
|
125
130
|
console.error(e);
|
|
@@ -167,18 +172,17 @@ export async function startSocketServer(
|
|
|
167
172
|
});
|
|
168
173
|
|
|
169
174
|
let realServer = net.createServer(socket => {
|
|
170
|
-
|
|
171
|
-
// we pipe. This should be very efficient, as pipe has insane throughput
|
|
172
|
-
// (100s of MB/s, easily, even on a terrible machine).
|
|
173
|
-
socket.once("data", buffer => {
|
|
175
|
+
function handleTLSHello(buffer: Buffer, packetCount: number): void | "more" {
|
|
174
176
|
// All HTTPS requests start with 22, and no HTTP requests start with 22,
|
|
175
177
|
// so we just need to read the first byte.
|
|
176
|
-
|
|
177
178
|
let server: https.Server | http.Server;
|
|
178
179
|
if (buffer[0] !== 22) {
|
|
179
180
|
server = httpServer;
|
|
180
181
|
} else {
|
|
181
182
|
let data = parseTLSHello(buffer);
|
|
183
|
+
if (data.missingBytes > 0) {
|
|
184
|
+
return "more";
|
|
185
|
+
}
|
|
182
186
|
let sni = data.extensions.filter(x => x.type === SNIType).flatMap(x => parseSNIExtension(x.data))[0];
|
|
183
187
|
if (!SocketFunction.silent) {
|
|
184
188
|
console.log(`Received TCP connection with SNI ${JSON.stringify(sni)}`);
|
|
@@ -189,7 +193,21 @@ export async function startSocketServer(
|
|
|
189
193
|
// NOTE: Messages aren't dequeued until the current handler finishes, so we don't need to pause the socket or anything.
|
|
190
194
|
server.emit("connection", socket);
|
|
191
195
|
socket.unshift(buffer);
|
|
192
|
-
}
|
|
196
|
+
}
|
|
197
|
+
let buffers: Buffer[] = [];
|
|
198
|
+
function getNextData() {
|
|
199
|
+
// NOTE: ONCE is used, so we only look at the first buffer, and then after that
|
|
200
|
+
// we pipe. This should be very efficient, as pipe has insane throughput
|
|
201
|
+
// (100s of MB/s, easily, even on a terrible machine).
|
|
202
|
+
socket.once("data", buffer => {
|
|
203
|
+
buffers.push(buffer);
|
|
204
|
+
let result = handleTLSHello(Buffer.concat(buffers), buffers.length);
|
|
205
|
+
if (result === "more") {
|
|
206
|
+
getNextData();
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
getNextData();
|
|
193
211
|
socket.on("error", (e) => {
|
|
194
212
|
console.error(`Exposed socket error, ${e.stack}`);
|
|
195
213
|
});
|
package/src/websocketFactory.ts
CHANGED
|
@@ -1,51 +1,57 @@
|
|
|
1
|
-
import ws from "ws";
|
|
2
|
-
import tls from "tls";
|
|
3
|
-
import { isNode } from "./misc";
|
|
4
|
-
import { SenderInterface } from "./CallFactory";
|
|
5
|
-
import { getTrustedCertificates } from "./certStore";
|
|
6
|
-
import { getNodeIdLocation } from "./nodeCache";
|
|
7
|
-
import debugbreak from "debugbreak";
|
|
8
|
-
import { SocketFunction } from "../SocketFunction";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
1
|
+
import ws from "ws";
|
|
2
|
+
import tls from "tls";
|
|
3
|
+
import { isNode } from "./misc";
|
|
4
|
+
import { SenderInterface } from "./CallFactory";
|
|
5
|
+
import { getTrustedCertificates } from "./certStore";
|
|
6
|
+
import { getNodeIdLocation } from "./nodeCache";
|
|
7
|
+
import debugbreak from "debugbreak";
|
|
8
|
+
import { SocketFunction } from "../SocketFunction";
|
|
9
|
+
|
|
10
|
+
export function getTLSSocket(webSocket: ws.WebSocket) {
|
|
11
|
+
return (webSocket as any)._socket as tls.TLSSocket;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** NOTE: We create a factory, which embeds the key/cert information. Otherwise retries might use
|
|
15
|
+
* a different key/cert context.
|
|
16
|
+
*/
|
|
17
|
+
export function createWebsocketFactory(): (nodeId: string) => SenderInterface {
|
|
18
|
+
|
|
19
|
+
if (!isNode()) {
|
|
20
|
+
return (nodeId: string) => {
|
|
21
|
+
let location = getNodeIdLocation(nodeId);
|
|
22
|
+
if (!location) throw new Error(`Cannot connect to ${nodeId}, no address known`);
|
|
23
|
+
let { address, port } = location;
|
|
24
|
+
|
|
25
|
+
if (!SocketFunction.silent) {
|
|
26
|
+
console.log(`Connecting to ${address}:${port}`);
|
|
27
|
+
}
|
|
28
|
+
return new WebSocket(`wss://${address}:${port}`);
|
|
29
|
+
};
|
|
30
|
+
} else {
|
|
31
|
+
return (nodeId: string) => {
|
|
32
|
+
let location = getNodeIdLocation(nodeId);
|
|
33
|
+
if (!location) throw new Error(`Cannot connect to ${nodeId}, no address known`);
|
|
34
|
+
let { address, port } = location;
|
|
35
|
+
|
|
36
|
+
if (!SocketFunction.silent) {
|
|
37
|
+
console.log(`Connecting to ${address}:${port}`);
|
|
38
|
+
}
|
|
39
|
+
let webSocket = new ws.WebSocket(`wss://${address}:${port}`, undefined, {
|
|
40
|
+
ca: getTrustedCertificates(),
|
|
41
|
+
// createConnection(options, oncreate) {
|
|
42
|
+
// // NOTE: If our latency is 500ms, with 10MB/s, then we need a high water
|
|
43
|
+
// // mark of at least 5MB, otherwise our connection is slowed down.
|
|
44
|
+
// //(options as any).writableHighWaterMark = 5 * 1024 * 1024;
|
|
45
|
+
// return tls.connect(options as any, oncreate as any);
|
|
46
|
+
// },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// NOTE: Little setup is done here, because Sometimes websockets are created here,
|
|
50
|
+
// and sometimes via incoming connections, We should do most setup in
|
|
51
|
+
// CallFactory.ts:initializeWebsocket
|
|
52
|
+
|
|
53
|
+
return webSocket;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
package/time/trueTimeShim.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SocketFunction } from "../SocketFunction";
|
|
2
|
+
import { blue, green, red, yellow } from "../src/formatting/logColors";
|
|
2
3
|
import { isNode } from "../src/misc";
|
|
3
4
|
|
|
4
5
|
module.allowclient = true;
|
|
@@ -121,6 +122,13 @@ async function updateTimeOffset() {
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
let prevOffset = trueTimeOffset;
|
|
125
|
+
let offsetRound = Math.abs(Math.round(offset));
|
|
126
|
+
let offsetColored = (
|
|
127
|
+
Math.abs(offset) > 600 && red(offsetRound + "ms")
|
|
128
|
+
|| Math.abs(offset) > 300 && yellow(offsetRound + "ms")
|
|
129
|
+
|| green(offsetRound + "ms")
|
|
130
|
+
);
|
|
131
|
+
console.log(`${blue("Synchronized time")}, local clock was ${offset > 0 ? "behind" : "ahead"} by ${offsetColored}`);
|
|
124
132
|
for (let i = 0; i < currentSmearCount; i++) {
|
|
125
133
|
let fraction = (i + 1) / currentSmearCount;
|
|
126
134
|
trueTimeOffset = prevOffset * (1 - fraction) + offset * fraction;
|