socket-function 0.8.36 → 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.
@@ -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, (configArg as any)[key]]);
279
+ return keys.flatMap(key => [key, configArg[key]]);
274
280
  }
275
281
  let output = Object.assign(
276
282
  ((configArg: object) => {
@@ -1,7 +1,8 @@
1
- import { CallContextType, CallerContext, CallType, ClientHookContext, FullCallType, HookContext, SocketExposedInterface, SocketExposedInterfaceClass, SocketExposedShape, SocketFunctionClientHook, SocketFunctionHook, SocketRegistered } from "../SocketFunctionTypes";
1
+ import { CallerContext, CallType, ClientHookContext, FullCallType, HookContext, SocketExposedInterface, SocketExposedInterfaceClass, SocketExposedShape, SocketFunctionClientHook, SocketFunctionHook, SocketRegistered } from "../SocketFunctionTypes";
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]: {
@@ -41,14 +42,13 @@ export async function performLocalCall(
41
42
  throw new Error(`Function ${call.functionName} does not exist`);
42
43
  }
43
44
 
44
- let curContext: CallContextType = {};
45
- let serverContext = await runServerHooks(call, { caller, curContext, getCaller: () => caller }, functionShape);
45
+ let serverContext = await runServerHooks(call, caller, functionShape);
46
46
  if ("overrideResult" in serverContext) {
47
47
  return serverContext.overrideResult;
48
48
  }
49
49
 
50
50
  // NOTE: We purposely don't await inside _setSocketContext, so the context is reset synchronously
51
- let result = _setSocketContext(curContext, caller, () => {
51
+ let result = _setSocketContext(caller, () => {
52
52
  return controller[call.functionName](...call.args);
53
53
  });
54
54
 
@@ -93,11 +93,12 @@ export function unregisterGlobalClientHook(hook: SocketFunctionClientHook) {
93
93
  }
94
94
  }
95
95
 
96
- export async function runClientHooks(
96
+ export const runClientHooks = measureWrap(async function runClientHooks(
97
97
  callType: FullCallType,
98
98
  hooks: SocketExposedShape[""],
99
+ connectionId: { nodeId: string },
99
100
  ): Promise<ClientHookContext> {
100
- let context: ClientHookContext = { call: callType };
101
+ let context: ClientHookContext = { call: callType, connectionId };
101
102
 
102
103
  let clientHooks = (
103
104
  globalClientHooks
@@ -114,20 +115,21 @@ export async function runClientHooks(
114
115
  break;
115
116
  }
116
117
  }
118
+
117
119
  return context;
118
- }
120
+ });
119
121
 
120
- async function runServerHooks(
122
+ export const runServerHooks = measureWrap(async function runServerHooks(
121
123
  callType: FullCallType,
122
- context: SocketRegistered["context"],
124
+ caller: CallerContext,
123
125
  hooks: SocketExposedShape[""],
124
126
  ): Promise<HookContext> {
125
- let hookContext: HookContext = { call: callType, context };
127
+ let hookContext: HookContext = { call: callType };
126
128
  for (let hook of globalHooks.concat(hooks.hooks || [])) {
127
- await hook(hookContext);
129
+ await _setSocketContext(caller, () => hook(hookContext));
128
130
  if ("overrideResult" in hookContext) {
129
131
  break;
130
132
  }
131
133
  }
132
134
  return hookContext;
133
- }
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.startsWith("client:")) {
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.startsWith("client:")) {
39
+ if (isClientNodeId(nodeId)) {
37
40
  return undefined;
38
41
  }
39
42
  let [address, port] = nodeId.split(":");
@@ -59,14 +62,17 @@ export function registerNodeClient(callFactory: CallFactory) {
59
62
  startCleanupLoop();
60
63
  }
61
64
 
62
- export function getCreateCallFactoryLocation(nodeId: string, mountedNodeId: string): MaybePromise<CallFactory> {
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, mountedNodeId);
68
+ callFactory = createCallFactory(undefined, nodeId);
66
69
  nodeCache.set(nodeId, callFactory);
67
70
  }
68
71
  return callFactory;
69
72
  }
73
+ export function getCallFactory(nodeId: string): MaybePromise<CallFactory | undefined> {
74
+ return nodeCache.get(nodeId);
75
+ }
70
76
 
71
77
  const startCleanupLoop = lazy(() => {
72
78
  (async () => {