socket-function 0.8.37 → 0.8.38
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 +16 -4
- package/SocketFunctionTypes.ts +2 -0
- package/package.json +4 -2
- package/require/require.js +8 -2
- package/src/CallFactory.ts +46 -13
- package/src/JSONLACKS/JSONLACKS.generated.js +2202 -0
- package/src/JSONLACKS/JSONLACKS.generated.js.d.ts +1 -0
- package/src/JSONLACKS/JSONLACKS.pegjs +248 -0
- package/src/JSONLACKS/JSONLACKS.ts +376 -0
- package/src/batching.ts +94 -0
- package/src/caching.ts +7 -1
- package/src/callManager.ts +8 -5
- package/src/certStore.ts +2 -0
- package/src/formatting/colors.ts +79 -0
- package/src/formatting/format.ts +157 -0
- package/src/formatting/logColors.ts +18 -0
- package/src/misc.ts +74 -2
- package/src/nodeCache.ts +7 -4
- package/src/profiling/getOwnTime.ts +143 -0
- package/src/profiling/measure.ts +241 -0
- package/src/profiling/stats.ts +213 -0
- package/src/profiling/tcpLagProxy.ts +64 -0
- package/src/types.ts +1 -1
- package/src/webSocketServer.ts +24 -11
- package/src/websocketFactory.ts +7 -2
package/src/batching.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { isNode } from "./misc";
|
|
2
|
+
import { measureWrap } from "./profiling/measure";
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
"numbers" use setTimeout
|
|
6
|
+
"afterpromises" uses a microtask, see https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide
|
|
7
|
+
"afterio" uses setImmediate, which will be after all pending and all created promises
|
|
8
|
+
(in the browser it is likely setImmediate will be shimmed with setTimeout)
|
|
9
|
+
"immediate" uses setImmediate, but if not available uses "afterpromises"
|
|
10
|
+
- The ensures a prompt return, without resorting to setTimeout in the browser (which will cause
|
|
11
|
+
the callback to be delayed a frame).
|
|
12
|
+
*/
|
|
13
|
+
export type DelayType = number | "afterio" | "immediate" | "afterpromises";
|
|
14
|
+
export function delay(delayTime: DelayType): Promise<void> {
|
|
15
|
+
if (delayTime === "afterio") {
|
|
16
|
+
return new Promise<void>(resolve => setImmediate(resolve));
|
|
17
|
+
} else if (delayTime === "afterpromises") {
|
|
18
|
+
// NOTE: We use a promise here as it might be a bit easier to debug than queueMicrotask.
|
|
19
|
+
// It is equivalent though...
|
|
20
|
+
return Promise.resolve();
|
|
21
|
+
} else if (delayTime === "immediate") {
|
|
22
|
+
if (isNode()) {
|
|
23
|
+
return new Promise<void>(resolve => setImmediate(resolve));
|
|
24
|
+
} else {
|
|
25
|
+
return delay("afterpromises");
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
return new Promise<void>(resolve => setTimeout(resolve, delayTime));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function batchFunction<Arg, Result = void>(
|
|
33
|
+
config: {
|
|
34
|
+
delay: DelayType;
|
|
35
|
+
name?: string;
|
|
36
|
+
},
|
|
37
|
+
fnc: (arg: Arg[]) => (Promise<Result> | Result)
|
|
38
|
+
): (arg: Arg) => Promise<Result> {
|
|
39
|
+
fnc = measureWrap(fnc, config.name);
|
|
40
|
+
|
|
41
|
+
let prevPromise: Promise<Result> | undefined;
|
|
42
|
+
let batched: {
|
|
43
|
+
args: Arg[];
|
|
44
|
+
promise: Promise<Result>;
|
|
45
|
+
} | undefined;
|
|
46
|
+
return async arg => {
|
|
47
|
+
if (!batched) {
|
|
48
|
+
await prevPromise;
|
|
49
|
+
}
|
|
50
|
+
if (batched) {
|
|
51
|
+
batched.args.push(arg);
|
|
52
|
+
return await batched.promise;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let args: Arg[] = [arg];
|
|
56
|
+
let promise = Promise.resolve().then(async () => {
|
|
57
|
+
await delay(config.delay);
|
|
58
|
+
// After we call the function, we can no longer accept args
|
|
59
|
+
batched = undefined;
|
|
60
|
+
return await fnc(args);
|
|
61
|
+
});
|
|
62
|
+
batched = {
|
|
63
|
+
args,
|
|
64
|
+
promise,
|
|
65
|
+
};
|
|
66
|
+
// We need to prevent new calls from starting when the previous call can no longer accept
|
|
67
|
+
// args, BUT, before it has finished.
|
|
68
|
+
prevPromise = batched.promise;
|
|
69
|
+
|
|
70
|
+
return await promise;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function runInSerial<T extends (...args: any[]) => Promise<any>>(fnc: T): T {
|
|
75
|
+
let updateQueue: (() => void)[] = [];
|
|
76
|
+
|
|
77
|
+
return (async (...args: any[]) => {
|
|
78
|
+
const queueWasEmpty = updateQueue.length === 0;
|
|
79
|
+
if (!queueWasEmpty) {
|
|
80
|
+
// Wait for the previous promise to resolve
|
|
81
|
+
await new Promise<void>(resolve => updateQueue.push(resolve));
|
|
82
|
+
}
|
|
83
|
+
updateQueue.push(() => { });
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
return await fnc(...args);
|
|
87
|
+
} finally {
|
|
88
|
+
// Pop ourself off
|
|
89
|
+
updateQueue.shift();
|
|
90
|
+
// Resolve the next promise
|
|
91
|
+
updateQueue[0]?.();
|
|
92
|
+
}
|
|
93
|
+
}) as T;
|
|
94
|
+
}
|
package/src/caching.ts
CHANGED
|
@@ -34,6 +34,7 @@ export function cacheEmptyArray<T>(array: T[]): T[] {
|
|
|
34
34
|
export function cache<Output, Key>(getValue: (key: Key) => Output): {
|
|
35
35
|
(key: Key): Output;
|
|
36
36
|
clear(key: Key): void;
|
|
37
|
+
forceSet(key: Key, value: Output): void;
|
|
37
38
|
} {
|
|
38
39
|
let startingCalculating = new Set<Key>();
|
|
39
40
|
let values = new Map<Key, Output>();
|
|
@@ -54,6 +55,11 @@ export function cache<Output, Key>(getValue: (key: Key) => Output): {
|
|
|
54
55
|
}
|
|
55
56
|
cache.clear = (key: Key) => {
|
|
56
57
|
values.delete(key);
|
|
58
|
+
startingCalculating.delete(key);
|
|
59
|
+
};
|
|
60
|
+
cache.forceSet = (key: Key, value: Output) => {
|
|
61
|
+
values.set(key, value);
|
|
62
|
+
startingCalculating.add(key);
|
|
57
63
|
};
|
|
58
64
|
return cache;
|
|
59
65
|
}
|
|
@@ -270,7 +276,7 @@ export function cacheShallowConfigArgEqual<Fnc extends AnyFunction>(
|
|
|
270
276
|
}
|
|
271
277
|
let keys = Object.keys(configArg);
|
|
272
278
|
keys.sort();
|
|
273
|
-
return keys.flatMap(key => [key,
|
|
279
|
+
return keys.flatMap(key => [key, configArg[key]]);
|
|
274
280
|
}
|
|
275
281
|
let output = Object.assign(
|
|
276
282
|
((configArg: object) => {
|
package/src/callManager.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { CallerContext, CallType, ClientHookContext, FullCallType, HookContext,
|
|
|
2
2
|
import { _setSocketContext } from "../SocketFunction";
|
|
3
3
|
import { isNode } from "./misc";
|
|
4
4
|
import debugbreak from "debugbreak";
|
|
5
|
+
import { measureWrap } from "./profiling/measure";
|
|
5
6
|
|
|
6
7
|
let classes: {
|
|
7
8
|
[classGuid: string]: {
|
|
@@ -92,11 +93,12 @@ export function unregisterGlobalClientHook(hook: SocketFunctionClientHook) {
|
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
export async function runClientHooks(
|
|
96
|
+
export const runClientHooks = measureWrap(async function runClientHooks(
|
|
96
97
|
callType: FullCallType,
|
|
97
98
|
hooks: SocketExposedShape[""],
|
|
99
|
+
connectionId: { nodeId: string },
|
|
98
100
|
): Promise<ClientHookContext> {
|
|
99
|
-
let context: ClientHookContext = { call: callType };
|
|
101
|
+
let context: ClientHookContext = { call: callType, connectionId };
|
|
100
102
|
|
|
101
103
|
let clientHooks = (
|
|
102
104
|
globalClientHooks
|
|
@@ -113,10 +115,11 @@ export async function runClientHooks(
|
|
|
113
115
|
break;
|
|
114
116
|
}
|
|
115
117
|
}
|
|
118
|
+
|
|
116
119
|
return context;
|
|
117
|
-
}
|
|
120
|
+
});
|
|
118
121
|
|
|
119
|
-
async function runServerHooks(
|
|
122
|
+
export const runServerHooks = measureWrap(async function runServerHooks(
|
|
120
123
|
callType: FullCallType,
|
|
121
124
|
caller: CallerContext,
|
|
122
125
|
hooks: SocketExposedShape[""],
|
|
@@ -129,4 +132,4 @@ async function runServerHooks(
|
|
|
129
132
|
}
|
|
130
133
|
}
|
|
131
134
|
return hookContext;
|
|
132
|
-
}
|
|
135
|
+
});
|
package/src/certStore.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as tls from "tls";
|
|
2
|
+
import { sha256Hash } from "./misc";
|
|
2
3
|
|
|
3
4
|
let trustedCerts = new Set<string>();
|
|
4
5
|
let watchCallbacks = new Set<(certs: string[]) => void>();
|
|
@@ -14,6 +15,7 @@ export function trustCertificate(cert: string | Buffer) {
|
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
export function getTrustedCertificates(): string[] {
|
|
18
|
+
//console.log(`trustedCerts = ${Array.from(trustedCerts).map(x => sha256Hash(x).slice(0, 10))}`);
|
|
17
19
|
return tls.rootCertificates.concat(Array.from(trustedCerts));
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export type HSL = { h: number, s: number, l: number };
|
|
2
|
+
export function hslText(color: HSL): string {
|
|
3
|
+
return `hsl(${color.h}, ${color.s}%, ${color.l}%)`;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function hslToRGB(color: HSL) {
|
|
7
|
+
let { h, s, l } = color;
|
|
8
|
+
h /= 360;
|
|
9
|
+
s /= 100;
|
|
10
|
+
l /= 100;
|
|
11
|
+
let r, g, b;
|
|
12
|
+
if (s === 0) {
|
|
13
|
+
r = g = b = l; // achromatic
|
|
14
|
+
} else {
|
|
15
|
+
const hue2rgb = (p: number, q: number, t: number) => {
|
|
16
|
+
if (t < 0) t += 1;
|
|
17
|
+
if (t > 1) t -= 1;
|
|
18
|
+
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
19
|
+
if (t < 1 / 2) return q;
|
|
20
|
+
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
21
|
+
return p;
|
|
22
|
+
};
|
|
23
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
24
|
+
const p = 2 * l - q;
|
|
25
|
+
r = hue2rgb(p, q, h + 1 / 3);
|
|
26
|
+
g = hue2rgb(p, q, h);
|
|
27
|
+
b = hue2rgb(p, q, h - 1 / 3);
|
|
28
|
+
if (b < 0) {
|
|
29
|
+
b = 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { r: Math.floor(r * 255), g: Math.floor(g * 255), b: Math.floor(b * 255) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function hslToHex(color: HSL) {
|
|
36
|
+
let { r, g, b } = hslToRGB(color);
|
|
37
|
+
const toHex = (x: number) => {
|
|
38
|
+
const hex = x.toString(16);
|
|
39
|
+
return hex.length === 1 ? "0" + hex : hex;
|
|
40
|
+
};
|
|
41
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Consider
|
|
45
|
+
export function hslLightenGamma(hsl: HSL, fraction: number) {
|
|
46
|
+
let l = ((hsl.l ** 2) * (1 + fraction)) ** 0.5;
|
|
47
|
+
if (l > 255) l = 255;
|
|
48
|
+
if (!l || l < 0) l = 0;
|
|
49
|
+
return { h: hsl.h, s: hsl.s, l };
|
|
50
|
+
}
|
|
51
|
+
export function hslLightenLinear(hsl: HSL, lightness: number) {
|
|
52
|
+
let l = hsl.l + lightness;
|
|
53
|
+
if (l > 255) l = 255;
|
|
54
|
+
if (!l || l < 0) l = 0;
|
|
55
|
+
return { h: hsl.h, s: hsl.s, l };
|
|
56
|
+
}
|
|
57
|
+
export function hslDarkenGamma(hsl: HSL, fraction: number) {
|
|
58
|
+
return hslLightenGamma(hsl, -fraction);
|
|
59
|
+
}
|
|
60
|
+
export function hslDarkenLinear(hsl: HSL, lightness: number) {
|
|
61
|
+
return hslLightenLinear(hsl, -lightness);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function hslAddSaturate(hsl: HSL, saturation: number) {
|
|
65
|
+
// If something has ZERO saturation, the color is meaningless, so increasing the saturation
|
|
66
|
+
// will likely not give the desired impact. Instead, darken the grey, which should give the desire effect.
|
|
67
|
+
if (hsl.s === 0) {
|
|
68
|
+
return hslDarkenGamma(hsl, 0.2);
|
|
69
|
+
}
|
|
70
|
+
return { h: hsl.h, s: hsl.s + saturation, l: hsl.l };
|
|
71
|
+
}
|
|
72
|
+
export function hslSetSaturate(hsl: HSL, saturation: number) {
|
|
73
|
+
// If something has ZERO saturation, the color is meaningless, so increasing the saturation
|
|
74
|
+
// will likely not give the desired impact. Instead, set the lightness.
|
|
75
|
+
if (hsl.s === 0) {
|
|
76
|
+
return { h: hsl.h, s: hsl.s, l: 100 - saturation };
|
|
77
|
+
}
|
|
78
|
+
return { h: hsl.h, s: saturation, l: hsl.l };
|
|
79
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
export function formatTime(milliseconds: number | undefined): string {
|
|
2
|
+
if (typeof milliseconds !== "number") return "";
|
|
3
|
+
if (milliseconds === 0) return "0ms";
|
|
4
|
+
if (milliseconds < 0) {
|
|
5
|
+
return "-" + formatTime(-milliseconds);
|
|
6
|
+
}
|
|
7
|
+
if (milliseconds < 1 / 1000) {
|
|
8
|
+
return formatMaxDecimals(milliseconds * 1000 * 1000, 3) + "ns";
|
|
9
|
+
} else if (milliseconds < 1) {
|
|
10
|
+
return formatMaxDecimals(milliseconds * 1000, 3) + "us";
|
|
11
|
+
} else if (milliseconds < 1000) {
|
|
12
|
+
return formatMaxDecimals(milliseconds, 3) + "ms";
|
|
13
|
+
// Use seconds until we have 10 minutes, as decimal minutes are confusing
|
|
14
|
+
} else if (milliseconds < 1000 * 60 * 10) {
|
|
15
|
+
return formatMaxDecimals(milliseconds / 1000, 3) + "s";
|
|
16
|
+
} else if (milliseconds < 1000 * 60 * 60) {
|
|
17
|
+
return formatMaxDecimals(milliseconds / 1000 / 60, 3) + "m";
|
|
18
|
+
} else if (milliseconds < 1000 * 60 * 60 * 24) {
|
|
19
|
+
return formatMaxDecimals(milliseconds / 1000 / 60 / 60, 3) + "h";
|
|
20
|
+
// } else if (milliseconds < 1000 * 60 * 60 * 24 * 10) {
|
|
21
|
+
// let remaining = Math.round(milliseconds / 1000);
|
|
22
|
+
// let seconds = remaining % 60;
|
|
23
|
+
// remaining -= seconds;
|
|
24
|
+
// remaining /= 60;
|
|
25
|
+
// let minutes = remaining % 60;
|
|
26
|
+
// remaining -= minutes;
|
|
27
|
+
// remaining /= 60;
|
|
28
|
+
// let hours = remaining;
|
|
29
|
+
// remaining -= hours;
|
|
30
|
+
// remaining /= 24;
|
|
31
|
+
// let days = remaining;
|
|
32
|
+
// let time = `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
33
|
+
// if (days > 0) {
|
|
34
|
+
// if (days === 1) {
|
|
35
|
+
// time = `1 day ${time}`;
|
|
36
|
+
// } else {
|
|
37
|
+
// time = `${days} days ${time}`;
|
|
38
|
+
// }
|
|
39
|
+
// }
|
|
40
|
+
// return time;
|
|
41
|
+
} else {
|
|
42
|
+
let days = Math.round(milliseconds / 1000 / 60 / 60 / 24);
|
|
43
|
+
return `${days} days`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getTargetDecimals(maxAbsoluteValue: number, targetDigits: number) {
|
|
48
|
+
let intDigits = Math.floor(Math.log10(maxAbsoluteValue) + 1);
|
|
49
|
+
if (intDigits < 0) intDigits = 1;
|
|
50
|
+
let decimalDigits = targetDigits - intDigits;
|
|
51
|
+
// Happens if the number is so close to having too many digits that Math.log10 rounds it over.
|
|
52
|
+
if (decimalDigits < 0) {
|
|
53
|
+
decimalDigits = 0;
|
|
54
|
+
}
|
|
55
|
+
return decimalDigits;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Adds decimal digits to reach digits. If the number is simply too large, it won't remove
|
|
59
|
+
* digits, there will instead just be no decimal point.
|
|
60
|
+
*/
|
|
61
|
+
export function formatMaxDecimals(num: number, targetDigits: number, maxAbsoluteValue?: number, exactDecimals?: number): string {
|
|
62
|
+
if (typeof num !== "number") return "0";
|
|
63
|
+
// toFixed has a max of 100 digits
|
|
64
|
+
if (targetDigits > 100) targetDigits = 100;
|
|
65
|
+
if (!Number.isFinite(num)) return num.toFixed(targetDigits);
|
|
66
|
+
|
|
67
|
+
if (num < 0) return formatMaxDecimals(-num, targetDigits, maxAbsoluteValue, exactDecimals);
|
|
68
|
+
|
|
69
|
+
// TIMING:
|
|
70
|
+
// ~50ns toString
|
|
71
|
+
// ~400ns toLocaleString
|
|
72
|
+
// ~500ns toLocaleString("en-us")
|
|
73
|
+
// ~20us toLocaleString("en-us", { maximumFractionDigits: 2 })
|
|
74
|
+
// So, we are avoiding using toLocaleString, for now.
|
|
75
|
+
|
|
76
|
+
maxAbsoluteValue = maxAbsoluteValue ?? Math.abs(num);
|
|
77
|
+
|
|
78
|
+
let targetDecimals = exactDecimals ?? getTargetDecimals(maxAbsoluteValue, targetDigits);
|
|
79
|
+
let text = num.toFixed(targetDecimals);
|
|
80
|
+
let parts = text.split(".");
|
|
81
|
+
let integer = parts[0];
|
|
82
|
+
let decimals = parts[1] ?? "";
|
|
83
|
+
|
|
84
|
+
if (exactDecimals) {
|
|
85
|
+
while (decimals.length < exactDecimals) {
|
|
86
|
+
decimals += "0";
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
while (decimals[decimals.length - 1] === "0") {
|
|
90
|
+
decimals = decimals.slice(0, -1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let output = "";
|
|
95
|
+
|
|
96
|
+
// NOTE: ONLY add comma groups if it is > 4 digits. As 4234K is easily read, and commas
|
|
97
|
+
// only really matter for numbers such as 4234523K, which is hard to read.
|
|
98
|
+
if (integer.length > 4) {
|
|
99
|
+
for (let i = integer.length; i > 0; i -= 3) {
|
|
100
|
+
let start = i - 3;
|
|
101
|
+
if (start < 0) start = 0;
|
|
102
|
+
let str = integer.slice(start, i);
|
|
103
|
+
if (output) {
|
|
104
|
+
output = "," + output;
|
|
105
|
+
}
|
|
106
|
+
output = str + output;
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
output = integer;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (decimals) {
|
|
113
|
+
output += "." + decimals;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return output;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Actually formats any number, including decimals, by using K, M and B suffixes to get smaller values
|
|
120
|
+
* TODO: Support uK, uM and uB suffixes for very small numbers?
|
|
121
|
+
*/
|
|
122
|
+
export function formatNumber(count: number | undefined, maxAbsoluteValue?: number, noDecimal?: boolean, specialCurrency?: boolean): string {
|
|
123
|
+
if (typeof count !== "number") return "0";
|
|
124
|
+
if (count < 0) {
|
|
125
|
+
return "-" + formatNumber(-count, maxAbsoluteValue, noDecimal, specialCurrency);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
maxAbsoluteValue = maxAbsoluteValue ?? Math.abs(count);
|
|
129
|
+
|
|
130
|
+
// NOTE: We don't switch units as soon as we possible can, because...
|
|
131
|
+
// 3.594 vs 3.584 is harder to quickly distinguish compared to 3594 and 3584,
|
|
132
|
+
// the decimal simply makes it harder to read, and larger.
|
|
133
|
+
const extraFactor = 10;
|
|
134
|
+
let divisor = 1;
|
|
135
|
+
let suffix = "";
|
|
136
|
+
let currencyDecimalsNeeded = false;
|
|
137
|
+
if (maxAbsoluteValue < 1000 * extraFactor) {
|
|
138
|
+
if (specialCurrency) {
|
|
139
|
+
currencyDecimalsNeeded = true;
|
|
140
|
+
}
|
|
141
|
+
} else if (maxAbsoluteValue < 1000 * 1000 * extraFactor) {
|
|
142
|
+
suffix = "K";
|
|
143
|
+
divisor = 1000;
|
|
144
|
+
} else if (maxAbsoluteValue < 1000 * 1000 * 1000 * extraFactor) {
|
|
145
|
+
suffix = "M";
|
|
146
|
+
divisor = 1000 * 1000;
|
|
147
|
+
} else {
|
|
148
|
+
suffix = "B";
|
|
149
|
+
divisor = 1000 * 1000 * 1000;
|
|
150
|
+
}
|
|
151
|
+
count /= divisor;
|
|
152
|
+
maxAbsoluteValue /= divisor;
|
|
153
|
+
|
|
154
|
+
let maxDecimals = noDecimal ? 0 : 3;
|
|
155
|
+
|
|
156
|
+
return formatMaxDecimals(count, maxDecimals, maxAbsoluteValue, currencyDecimalsNeeded ? 2 : undefined) + suffix;
|
|
157
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { hslToHex, hslToRGB } from "./colors";
|
|
2
|
+
|
|
3
|
+
function ansiHSL(h: number, s: number, l: number, text: string): string {
|
|
4
|
+
let { r, g, b } = hslToRGB({ h, s, l });
|
|
5
|
+
return ansiRGB(r, g, b, text);
|
|
6
|
+
}
|
|
7
|
+
function ansiRGB(r: number, g: number, b: number, text: string): string {
|
|
8
|
+
return `\x1b[38;2;${r};${g};${b}m${text}\x1b[39m`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const lightness = 68;
|
|
12
|
+
export const blue = ansiHSL.bind(null, 235, 100, lightness);
|
|
13
|
+
export const red = ansiHSL.bind(null, 0, 100, lightness);
|
|
14
|
+
export const green = (text: string) => `\x1b[32m${text}\x1b[39m`; // chalk.hsl(120, 100, lightness).bind(chalk);
|
|
15
|
+
export const yellow = (text: string) => `\x1b[33m${text}\x1b[39m`;
|
|
16
|
+
export const white = ansiHSL.bind(null, 0, 0, 80);
|
|
17
|
+
|
|
18
|
+
export const magenta = (text: string) => `\x1b[35m${text}\x1b[39m`;
|
package/src/misc.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as crypto from "crypto";
|
|
2
|
-
import { MaybePromise } from "./types";
|
|
2
|
+
import { canHaveChildren, MaybePromise } from "./types";
|
|
3
3
|
|
|
4
4
|
export type Watchable<T> = (callback: (value: T) => void) => MaybePromise<void>;
|
|
5
5
|
|
|
@@ -10,9 +10,12 @@ export function convertErrorStackToError(error: string): Error {
|
|
|
10
10
|
return errorObj;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function sha256Hash(buffer: Buffer | string) {
|
|
13
|
+
export function sha256Hash(buffer: Buffer | string): string {
|
|
14
14
|
return crypto.createHash("sha256").update(buffer).digest("hex");
|
|
15
15
|
}
|
|
16
|
+
export function sha256HashBuffer(buffer: Buffer | string): Buffer {
|
|
17
|
+
return crypto.createHash("sha256").update(buffer).digest();
|
|
18
|
+
}
|
|
16
19
|
/** Async, but works both clientside and serverside. */
|
|
17
20
|
export async function sha256HashPromise(buffer: Buffer) {
|
|
18
21
|
if (isNode()) {
|
|
@@ -22,6 +25,14 @@ export async function sha256HashPromise(buffer: Buffer) {
|
|
|
22
25
|
return Buffer.from(buf).toString("hex");
|
|
23
26
|
}
|
|
24
27
|
}
|
|
28
|
+
export async function sha256BufferPromise(buffer: Buffer): Promise<Buffer> {
|
|
29
|
+
if (isNode()) {
|
|
30
|
+
return crypto.createHash("sha256").update(buffer).digest();
|
|
31
|
+
} else {
|
|
32
|
+
let buf = await window.crypto.subtle.digest("SHA-256", buffer);
|
|
33
|
+
return Buffer.from(buf);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
25
36
|
|
|
26
37
|
|
|
27
38
|
export function arrayEqual(a: unknown[], b: unknown[]) {
|
|
@@ -68,6 +79,67 @@ export function formatNumberSuffixed(count: number): string {
|
|
|
68
79
|
return Math.round(count).toString() + suffix;
|
|
69
80
|
}
|
|
70
81
|
|
|
82
|
+
export function list(count: number) {
|
|
83
|
+
let arr: number[] = [];
|
|
84
|
+
for (let i = 0; i < count; i++) {
|
|
85
|
+
arr.push(i);
|
|
86
|
+
}
|
|
87
|
+
return arr;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function recursiveFreeze<T>(obj: T): T {
|
|
91
|
+
if (!canHaveChildren(obj)) return obj;
|
|
92
|
+
let visited = new Set<unknown>();
|
|
93
|
+
function iterate(obj: unknown) {
|
|
94
|
+
if (!canHaveChildren(obj)) return;
|
|
95
|
+
if (visited.has(obj)) return;
|
|
96
|
+
visited.add(obj);
|
|
97
|
+
Object.freeze(obj);
|
|
98
|
+
let keys = getKeys(obj);
|
|
99
|
+
for (let key of keys) {
|
|
100
|
+
iterate(obj[key]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
iterate(obj);
|
|
104
|
+
return obj;
|
|
105
|
+
}
|
|
106
|
+
export type ArrayBufferViewTypes = Uint8Array | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array | BigUint64Array | BigInt64Array | Float64Array | Float32Array | Uint8ClampedArray;
|
|
107
|
+
export type BufferType = ArrayBuffer | SharedArrayBuffer | ArrayBufferViewTypes;
|
|
108
|
+
export function isBufferType(obj: unknown): obj is BufferType {
|
|
109
|
+
if (typeof obj !== "object") return false;
|
|
110
|
+
if (!obj) return false;
|
|
111
|
+
if (ArrayBuffer.isView(obj)) return true;
|
|
112
|
+
if (obj instanceof ArrayBuffer) return true;
|
|
113
|
+
if (global.SharedArrayBuffer && obj instanceof global.SharedArrayBuffer) return true;
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
export function getKeys(obj: unknown) {
|
|
117
|
+
if (typeof obj !== "object" && typeof obj !== "function" || obj === null) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
if (obj instanceof MessagePort) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
let keyArray: PropertyKey[];
|
|
124
|
+
if (isBufferType(obj)) {
|
|
125
|
+
keyArray = [];
|
|
126
|
+
} else if (Array.isArray(obj)) {
|
|
127
|
+
// NOTE: We convert the indexes to strings, because that is what javascript does,
|
|
128
|
+
// and differing from it causes regressions that we simply cannot rectify (it breaks hashing
|
|
129
|
+
// consistency).
|
|
130
|
+
keyArray = Array(obj.length).fill(0).map((x, i) => String(i));
|
|
131
|
+
} else {
|
|
132
|
+
keyArray = Object.keys(obj);
|
|
133
|
+
}
|
|
134
|
+
for (let symbol of Object.getOwnPropertySymbols(obj)) {
|
|
135
|
+
let key = Symbol.keyFor(symbol);
|
|
136
|
+
if (key) {
|
|
137
|
+
keyArray.push(symbol);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return keyArray;
|
|
141
|
+
}
|
|
142
|
+
|
|
71
143
|
if (isNode()) {
|
|
72
144
|
// TODO: Find a better place for this...
|
|
73
145
|
process.on("unhandledRejection", async (reason: any, promise) => {
|
package/src/nodeCache.ts
CHANGED
|
@@ -21,19 +21,22 @@ export function getNodeId(domain: string, port: number): string {
|
|
|
21
21
|
export function getClientNodeId(address: string): string {
|
|
22
22
|
return `client:${address}:${Date.now()}:${Math.random()}`;
|
|
23
23
|
}
|
|
24
|
+
export function isClientNodeId(nodeId: string): boolean {
|
|
25
|
+
return nodeId.startsWith("client:");
|
|
26
|
+
}
|
|
24
27
|
/** Will always be available, even if getNodeIdLocation is not (as we don't always have the port,
|
|
25
28
|
* but we should always have an address).
|
|
26
29
|
* - Rarely used, as for logging you can just log the nodeId. ALSO, it isn't sufficient to reconnect, as the port is also needed!
|
|
27
30
|
* */
|
|
28
31
|
export function getNodeIdIP(nodeId: string): string {
|
|
29
|
-
if (nodeId
|
|
32
|
+
if (isClientNodeId(nodeId)) {
|
|
30
33
|
return nodeId.split(":")[1];
|
|
31
34
|
}
|
|
32
35
|
return getNodeIdLocation(nodeId)!.address;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export function getNodeIdLocation(nodeId: string): { address: string, port: number; } | undefined {
|
|
36
|
-
if (nodeId
|
|
39
|
+
if (isClientNodeId(nodeId)) {
|
|
37
40
|
return undefined;
|
|
38
41
|
}
|
|
39
42
|
let [address, port] = nodeId.split(":");
|
|
@@ -59,10 +62,10 @@ export function registerNodeClient(callFactory: CallFactory) {
|
|
|
59
62
|
startCleanupLoop();
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
export function getCreateCallFactory(nodeId: string
|
|
65
|
+
export function getCreateCallFactory(nodeId: string): MaybePromise<CallFactory> {
|
|
63
66
|
let callFactory = nodeCache.get(nodeId);
|
|
64
67
|
if (callFactory === undefined) {
|
|
65
|
-
callFactory = createCallFactory(undefined, nodeId
|
|
68
|
+
callFactory = createCallFactory(undefined, nodeId);
|
|
66
69
|
nodeCache.set(nodeId, callFactory);
|
|
67
70
|
}
|
|
68
71
|
return callFactory;
|