socket-function 0.8.37 → 0.8.39
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 +23 -6
- package/SocketFunctionTypes.ts +2 -0
- package/package.json +4 -2
- package/require/RequireController.ts +8 -2
- package/require/require.js +17 -26
- package/src/CallFactory.ts +69 -14
- 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 +127 -0
- package/src/caching.ts +7 -1
- package/src/callManager.ts +8 -5
- package/src/certStore.ts +2 -0
- package/src/fixLargeNetworkCalls.ts +9 -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 +245 -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/SocketFunction.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { Args, MaybePromise } from "./src/types";
|
|
|
9
9
|
import { setDefaultHTTPCall } from "./src/callHTTPHandler";
|
|
10
10
|
import debugbreak from "debugbreak";
|
|
11
11
|
import { lazy } from "./src/caching";
|
|
12
|
+
import { delay } from "./src/batching";
|
|
12
13
|
|
|
13
14
|
module.allowclient = true;
|
|
14
15
|
|
|
@@ -28,10 +29,16 @@ type PickByType<T, Value> = {
|
|
|
28
29
|
|
|
29
30
|
export class SocketFunction {
|
|
30
31
|
public static logMessages = false;
|
|
32
|
+
|
|
33
|
+
public static MAX_MESSAGE_SIZE = 1024 * 1024 * 32;
|
|
31
34
|
public static compression: undefined | {
|
|
32
35
|
type: "gzip";
|
|
33
36
|
};
|
|
37
|
+
|
|
34
38
|
public static httpETagCache = false;
|
|
39
|
+
public static silent = true;
|
|
40
|
+
|
|
41
|
+
public static WIRE_WARN_TIME = 100;
|
|
35
42
|
|
|
36
43
|
private static onMountCallbacks = new Map<string, (() => MaybePromise<void>)[]>();
|
|
37
44
|
public static exposedClasses = new Set<string>();
|
|
@@ -70,7 +77,7 @@ export class SocketFunction {
|
|
|
70
77
|
return shape as any as SocketExposedShape;
|
|
71
78
|
});
|
|
72
79
|
|
|
73
|
-
|
|
80
|
+
void Promise.resolve().then(() => {
|
|
74
81
|
registerClass(classGuid, instance as SocketExposedInterface, getShape());
|
|
75
82
|
});
|
|
76
83
|
|
|
@@ -82,14 +89,14 @@ export class SocketFunction {
|
|
|
82
89
|
console.log(`START\t\t\t${classGuid}.${functionName}`);
|
|
83
90
|
}
|
|
84
91
|
try {
|
|
85
|
-
let callFactory = await getCreateCallFactory(nodeId
|
|
92
|
+
let callFactory = await getCreateCallFactory(nodeId);
|
|
86
93
|
|
|
87
94
|
let shapeObj = getShape()[functionName];
|
|
88
95
|
if (!shapeObj) {
|
|
89
96
|
throw new Error(`Function ${functionName} is not in shape`);
|
|
90
97
|
}
|
|
91
98
|
|
|
92
|
-
let hookResult = await runClientHooks(call, shapeObj as SocketExposedShape[""]);
|
|
99
|
+
let hookResult = await runClientHooks(call, shapeObj as SocketExposedShape[""], callFactory.connectionId);
|
|
93
100
|
|
|
94
101
|
if ("overrideResult" in hookResult) {
|
|
95
102
|
return hookResult.overrideResult;
|
|
@@ -109,7 +116,7 @@ export class SocketFunction {
|
|
|
109
116
|
_classGuid: classGuid,
|
|
110
117
|
};
|
|
111
118
|
|
|
112
|
-
|
|
119
|
+
void Promise.resolve().then(() => {
|
|
113
120
|
let onMount = getDefaultHooks?.().onMount;
|
|
114
121
|
if (onMount) {
|
|
115
122
|
let callbacks = SocketFunction.onMountCallbacks.get(classGuid);
|
|
@@ -191,12 +198,22 @@ export class SocketFunction {
|
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
200
|
|
|
194
|
-
public static mountedNodeId: string = "
|
|
201
|
+
public static mountedNodeId: string = "";
|
|
202
|
+
public static mountedIP: string = "";
|
|
195
203
|
private static hasMounted = false;
|
|
196
204
|
public static async mount(config: SocketServerConfig) {
|
|
197
|
-
if (this.mountedNodeId
|
|
205
|
+
if (this.mountedNodeId) {
|
|
198
206
|
throw new Error("SocketFunction already mounted, mounting twice in one thread is not allowed.");
|
|
199
207
|
}
|
|
208
|
+
|
|
209
|
+
this.mountedIP = config.public ? "0.0.0.0" : "127.0.0.1";
|
|
210
|
+
if (config.ip) {
|
|
211
|
+
this.mountedIP = config.ip;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Wait for any additionals functions to expose themselves
|
|
215
|
+
await delay("immediate");
|
|
216
|
+
|
|
200
217
|
this.mountedNodeId = await startSocketServer(config);
|
|
201
218
|
this.hasMounted = true;
|
|
202
219
|
for (let classGuid of SocketFunction.exposedClasses) {
|
package/SocketFunctionTypes.ts
CHANGED
|
@@ -57,6 +57,7 @@ export type ClientHookContext<ExposedType extends SocketExposedInterface = Socke
|
|
|
57
57
|
call: FullCallType;
|
|
58
58
|
// If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
|
|
59
59
|
overrideResult?: unknown;
|
|
60
|
+
connectionId: { nodeId: string };
|
|
60
61
|
};
|
|
61
62
|
export interface SocketFunctionClientHook<ExposedType extends SocketExposedInterface = SocketExposedInterface> {
|
|
62
63
|
(config: ClientHookContext<ExposedType>): MaybePromise<void>;
|
|
@@ -85,5 +86,6 @@ export type CallerContextBase = {
|
|
|
85
86
|
// The nodeId they contacted. This is useful to determine their intention (otherwise
|
|
86
87
|
// requests can be redirected to us and would accept them, even though they are being
|
|
87
88
|
// blatantly MITMed).
|
|
89
|
+
// IF they are the server, calling us back, then this will just be ""
|
|
88
90
|
localNodeId: string;
|
|
89
91
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "socket-function",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.39",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"mobx": "^6.6.2",
|
|
10
10
|
"node-forge": "https://github.com/sliftist/forge#name",
|
|
11
11
|
"preact": "^10.10.6",
|
|
12
|
-
"
|
|
12
|
+
"rdtsc-now": "^0.3.0",
|
|
13
|
+
"typenode": "^4.9.4-c",
|
|
13
14
|
"ws": "^8.8.0"
|
|
14
15
|
},
|
|
15
16
|
"scripts": {
|
|
@@ -21,6 +22,7 @@
|
|
|
21
22
|
"@types/node-forge": "^1.3.1",
|
|
22
23
|
"@types/ws": "^8.5.3",
|
|
23
24
|
"debugbreak": "^0.6.5",
|
|
25
|
+
"pegjs": "^0.10.0",
|
|
24
26
|
"typedev": "^0.1.1"
|
|
25
27
|
}
|
|
26
28
|
}
|
|
@@ -13,6 +13,11 @@ declare global {
|
|
|
13
13
|
/** Indicates the module is allowed clientside. */
|
|
14
14
|
allowclient?: boolean;
|
|
15
15
|
|
|
16
|
+
/** Causes the module to not preload, requiring `await import()` for it to load correctly
|
|
17
|
+
* - Shouldn't be set recursively, otherwise nested packages will break.
|
|
18
|
+
*/
|
|
19
|
+
lazyload?: boolean;
|
|
20
|
+
|
|
16
21
|
/** Indicates the module is definitely not allowed clientside */
|
|
17
22
|
serveronly?: boolean;
|
|
18
23
|
|
|
@@ -118,7 +123,8 @@ class RequireControllerBase {
|
|
|
118
123
|
let modules: {
|
|
119
124
|
[resolvedPath: string]: SerializedModule;
|
|
120
125
|
} = Object.create(null);
|
|
121
|
-
function addModule(module: NodeJS.Module) {
|
|
126
|
+
function addModule(module: NodeJS.Module, rootImport = false) {
|
|
127
|
+
if (!rootImport && module.lazyload) return;
|
|
122
128
|
if (!module.requireControllerSeqNum) {
|
|
123
129
|
module.requireControllerSeqNum = nextModuleSeqNum++;
|
|
124
130
|
}
|
|
@@ -223,7 +229,7 @@ class RequireControllerBase {
|
|
|
223
229
|
clientModule = createNotFoundModule(`Module ${pathRequest} (resolved to ${resolvedPath}) is not allowed clientside (set module.allowclient in it, or call setFlag when it is imported).`);
|
|
224
230
|
}
|
|
225
231
|
|
|
226
|
-
addModule(clientModule);
|
|
232
|
+
addModule(clientModule, true);
|
|
227
233
|
}
|
|
228
234
|
|
|
229
235
|
return { requestsResolvedPaths, modules, requireSeqNumProcessId };
|
package/require/require.js
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
global: window,
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
|
|
20
19
|
// Not real modules, as we just define their exports
|
|
21
20
|
const builtInModuleExports = {
|
|
22
21
|
worker_threads: {
|
|
@@ -243,6 +242,15 @@
|
|
|
243
242
|
}
|
|
244
243
|
|
|
245
244
|
let resolvedPath = serializedModule.requests[request];
|
|
245
|
+
if (resolvedPath !== "NOTALLOWEDCLIENTSIDE" && !serializedModules[resolvedPath]) {
|
|
246
|
+
if (!asyncIsFine) {
|
|
247
|
+
console.warn(`Accessed unexpected module %c${request}%c in %c${module.id}%c\n\tTreating it as an async require.\n\tAll modules require synchronously clientside must be required serverside at a module level.`,
|
|
248
|
+
"color: red", "color: unset",
|
|
249
|
+
"color: red", "color: unset",
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return rootRequire(resolvedPath);
|
|
253
|
+
}
|
|
246
254
|
|
|
247
255
|
let exportsOverride = undefined;
|
|
248
256
|
if (resolvedPath === "NOTALLOWEDCLIENTSIDE" || !serializedModules[resolvedPath].allowclient) {
|
|
@@ -253,6 +261,7 @@
|
|
|
253
261
|
if (property === "__esModule") return undefined;
|
|
254
262
|
// NOTE: Return a toString that evaluates to "" so we can EXPLICITLY detect non-loaded modules
|
|
255
263
|
if (property === unloadedModule) return true;
|
|
264
|
+
if (property === "default") return exportsOverride;
|
|
256
265
|
|
|
257
266
|
throw new Error(`Module ${childId} is serverside only. Tried to access ${property} from ${module.id}`);
|
|
258
267
|
}
|
|
@@ -263,6 +272,7 @@
|
|
|
263
272
|
if (property === "__esModule") return undefined;
|
|
264
273
|
// NOTE: Return a toString that evaluates to "" so we can EXPLICITLY detect non-loaded modules
|
|
265
274
|
if (property === unloadedModule) return true;
|
|
275
|
+
if (property === "default") return exportsOverride;
|
|
266
276
|
|
|
267
277
|
serializedModule;
|
|
268
278
|
|
|
@@ -336,6 +346,7 @@
|
|
|
336
346
|
module.id = resolvedId;
|
|
337
347
|
module.filename = serializedModule?.filename;
|
|
338
348
|
module.exports = {};
|
|
349
|
+
module.exports.default = module.exports;
|
|
339
350
|
module.children = [];
|
|
340
351
|
|
|
341
352
|
module.load = load;
|
|
@@ -382,19 +393,6 @@
|
|
|
382
393
|
|
|
383
394
|
let dirname = module.filename.replace(/\\/g, "/").split("/").slice(0, -1).join("/");
|
|
384
395
|
|
|
385
|
-
var __createBinding = (Object.create ? (function (o, m, k, k2) {
|
|
386
|
-
if (k2 === undefined) k2 = k;
|
|
387
|
-
Object.defineProperty(o, k2, { enumerable: true, get: function () { return m[k]; } });
|
|
388
|
-
}) : (function (o, m, k, k2) {
|
|
389
|
-
if (k2 === undefined) k2 = k;
|
|
390
|
-
o[k2] = m[k];
|
|
391
|
-
}));
|
|
392
|
-
var __setModuleDefault = (Object.create ? (function (o, v) {
|
|
393
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
394
|
-
}) : function (o, v) {
|
|
395
|
-
o["default"] = v;
|
|
396
|
-
});
|
|
397
|
-
|
|
398
396
|
let time = Date.now();
|
|
399
397
|
currentModuleEvaluationStack.push(module.filename);
|
|
400
398
|
try {
|
|
@@ -404,18 +402,11 @@
|
|
|
404
402
|
// which checks for unloadedModule and returns undefined in that case.
|
|
405
403
|
__importStar(mod) {
|
|
406
404
|
if (mod[unloadedModule]) return undefined;
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
__createBinding(result, mod, k);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
__setModuleDefault(result, mod);
|
|
417
|
-
return result;
|
|
418
|
-
}
|
|
405
|
+
return mod;
|
|
406
|
+
},
|
|
407
|
+
__importDefault(mod) {
|
|
408
|
+
return mod.default ? mod : { default: mod };
|
|
409
|
+
},
|
|
419
410
|
},
|
|
420
411
|
module.exports,
|
|
421
412
|
module.require,
|
package/src/CallFactory.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CallerContext, CallerContextBase, CallType, FullCallType } from "../SocketFunctionTypes";
|
|
2
2
|
import * as ws from "ws";
|
|
3
3
|
import { performLocalCall } from "./callManager";
|
|
4
|
-
import { convertErrorStackToError, formatNumberSuffixed, isNode } from "./misc";
|
|
4
|
+
import { convertErrorStackToError, formatNumberSuffixed, isNode, list } from "./misc";
|
|
5
5
|
import { createWebsocketFactory, getTLSSocket } from "./websocketFactory";
|
|
6
6
|
import { SocketFunction } from "../SocketFunction";
|
|
7
7
|
import { gzip } from "zlib";
|
|
@@ -9,6 +9,9 @@ import * as tls from "tls";
|
|
|
9
9
|
import { getClientNodeId, getNodeIdLocation, registerNodeClient } from "./nodeCache";
|
|
10
10
|
import debugbreak from "debugbreak";
|
|
11
11
|
import { lazy } from "./caching";
|
|
12
|
+
import { JSONLACKS } from "./JSONLACKS/JSONLACKS";
|
|
13
|
+
import { red } from "./formatting/logColors";
|
|
14
|
+
import { isSplitableArray } from "./fixLargeNetworkCalls";
|
|
12
15
|
|
|
13
16
|
const MIN_RETRY_DELAY = 1000;
|
|
14
17
|
|
|
@@ -37,6 +40,7 @@ export interface CallFactory {
|
|
|
37
40
|
// Trigger performLocalCall on the other side of the connection
|
|
38
41
|
performCall(call: CallType): Promise<unknown>;
|
|
39
42
|
onNextDisconnect(callback: () => void): void;
|
|
43
|
+
connectionId: { nodeId: string };
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
export interface SenderInterface {
|
|
@@ -56,8 +60,10 @@ export interface SenderInterface {
|
|
|
56
60
|
|
|
57
61
|
export async function createCallFactory(
|
|
58
62
|
webSocketBase: SenderInterface | undefined,
|
|
63
|
+
// The node id we are connecting to (or that connected to us)
|
|
59
64
|
nodeId: string,
|
|
60
|
-
|
|
65
|
+
// The node id that we were contacted on
|
|
66
|
+
localNodeId = "",
|
|
61
67
|
): Promise<CallFactory> {
|
|
62
68
|
let niceConnectionName = nodeId;
|
|
63
69
|
|
|
@@ -66,8 +72,6 @@ export async function createCallFactory(
|
|
|
66
72
|
|
|
67
73
|
const canReconnect = !!getNodeIdLocation(nodeId);
|
|
68
74
|
|
|
69
|
-
let lastReceivedSeqNum = 0;
|
|
70
|
-
|
|
71
75
|
let pendingCalls: Map<number, {
|
|
72
76
|
data: Buffer;
|
|
73
77
|
call: InternalCallType;
|
|
@@ -76,7 +80,7 @@ export async function createCallFactory(
|
|
|
76
80
|
// NOTE: It is important to make this as random as possible, to prevent
|
|
77
81
|
// reconnections dues to a process being reset causing seqNum collisions
|
|
78
82
|
// in return calls.
|
|
79
|
-
let nextSeqNum = Math.random();
|
|
83
|
+
let nextSeqNum = Date.now() + Math.random();
|
|
80
84
|
|
|
81
85
|
let lastConnectionAttempt = 0;
|
|
82
86
|
|
|
@@ -93,6 +97,7 @@ export async function createCallFactory(
|
|
|
93
97
|
let callFactory: CallFactory = {
|
|
94
98
|
nodeId,
|
|
95
99
|
lastClosed: 0,
|
|
100
|
+
connectionId: { nodeId },
|
|
96
101
|
onNextDisconnect,
|
|
97
102
|
async performCall(call: CallType) {
|
|
98
103
|
let seqNum = nextSeqNum++;
|
|
@@ -105,7 +110,37 @@ export async function createCallFactory(
|
|
|
105
110
|
seqNum,
|
|
106
111
|
compress: !!SocketFunction.compression,
|
|
107
112
|
};
|
|
108
|
-
let
|
|
113
|
+
let time = Date.now();
|
|
114
|
+
let data = Buffer.from(JSONLACKS.stringify(fullCall));
|
|
115
|
+
time = Date.now() - time;
|
|
116
|
+
if (time > SocketFunction.WIRE_WARN_TIME) {
|
|
117
|
+
console.log(red(`Slow serialize, took ${time}ms to serialize ${data.byteLength} bytes. For ${call.classGuid}.${call.functionName}`));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (data.byteLength > SocketFunction.MAX_MESSAGE_SIZE * 1.5) {
|
|
121
|
+
let splitArgIndex = call.args.findIndex(isSplitableArray);
|
|
122
|
+
if (splitArgIndex >= 0) {
|
|
123
|
+
let SPLIT_GROUPS = 10;
|
|
124
|
+
let splitArg = call.args[splitArgIndex] as unknown[];
|
|
125
|
+
let subCalls = list(SPLIT_GROUPS).map(index => {
|
|
126
|
+
let start = Math.floor(index / SPLIT_GROUPS * splitArg.length);
|
|
127
|
+
let end = Math.floor((index + 1) / SPLIT_GROUPS * splitArg.length);
|
|
128
|
+
return splitArg.slice(start, end);
|
|
129
|
+
}).filter(x => x.length > 0);
|
|
130
|
+
for (let splitList of subCalls) {
|
|
131
|
+
let subCall = { ...call };
|
|
132
|
+
subCall.args = subCall.args.slice();
|
|
133
|
+
subCall.args[splitArgIndex] = splitList;
|
|
134
|
+
await callFactory.performCall(subCall);
|
|
135
|
+
}
|
|
136
|
+
// Eh... we COULD return the array of results, but... then the result would sometimes be an array,
|
|
137
|
+
// some times not, so, it is better to return a string which will make it more clear why it sometimes varies.
|
|
138
|
+
return "CALLS_SPLIT_DUE_TO_LARGE_ARGS";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
throw new Error(`Call too large to send (${call.classGuid}.${call.functionName}, size: ${data.byteLength} > ${SocketFunction.MAX_MESSAGE_SIZE}). If you need to handle very large static data use some external service, such as Backblaze B2 or AWS S3. Or consider fragmenting data at an application level, because sending large data will cause large lag spikes for other clients using this server. Or, if absolutely required, set SocketFunction.MAX_MESSAGE_SIZE to a higher value.`);
|
|
142
|
+
}
|
|
143
|
+
|
|
109
144
|
let resultPromise = new Promise((resolve, reject) => {
|
|
110
145
|
let callback = (result: InternalReturnType) => {
|
|
111
146
|
if (SocketFunction.logMessages) {
|
|
@@ -137,6 +172,7 @@ export async function createCallFactory(
|
|
|
137
172
|
registerOnce();
|
|
138
173
|
|
|
139
174
|
function onClose(error: string) {
|
|
175
|
+
callFactory.connectionId = { nodeId };
|
|
140
176
|
callFactory.lastClosed = Date.now();
|
|
141
177
|
webSocketPromise = undefined;
|
|
142
178
|
if (!canReconnect) {
|
|
@@ -181,7 +217,9 @@ export async function createCallFactory(
|
|
|
181
217
|
if (newWebSocket.readyState === 0 /* CONNECTING */) {
|
|
182
218
|
await new Promise<void>(resolve => {
|
|
183
219
|
newWebSocket.addEventListener("open", () => {
|
|
184
|
-
|
|
220
|
+
if (!SocketFunction.silent) {
|
|
221
|
+
console.log(`Connection established to ${niceConnectionName}`);
|
|
222
|
+
}
|
|
185
223
|
callFactory.isConnected = true;
|
|
186
224
|
resolve();
|
|
187
225
|
});
|
|
@@ -248,9 +286,15 @@ export async function createCallFactory(
|
|
|
248
286
|
(message as any) = Buffer.from(arrayBuffer);
|
|
249
287
|
}
|
|
250
288
|
|
|
251
|
-
let
|
|
289
|
+
let time = Date.now();
|
|
290
|
+
let call = JSONLACKS.parse(message.toString(), { extended: false }) as InternalCallType | InternalReturnType;
|
|
291
|
+
time = Date.now() - time;
|
|
292
|
+
|
|
252
293
|
if (call.isReturn) {
|
|
253
294
|
let callbackObj = pendingCalls.get(call.seqNum);
|
|
295
|
+
if (time > SocketFunction.WIRE_WARN_TIME) {
|
|
296
|
+
console.log(red(`Slow parse, took ${time}ms to parse ${message.length} bytes, for receieving result of call to ${callbackObj?.call.classGuid}.${callbackObj?.call.functionName}`));
|
|
297
|
+
}
|
|
254
298
|
if (!callbackObj) {
|
|
255
299
|
console.log(`Got return for unknown call ${call.seqNum}`);
|
|
256
300
|
return;
|
|
@@ -258,11 +302,9 @@ export async function createCallFactory(
|
|
|
258
302
|
call.resultSize = resultSize;
|
|
259
303
|
callbackObj.callback(call);
|
|
260
304
|
} else {
|
|
261
|
-
if (
|
|
262
|
-
console.log(`
|
|
263
|
-
return;
|
|
305
|
+
if (time > SocketFunction.WIRE_WARN_TIME) {
|
|
306
|
+
console.log(red(`Slow parse, took ${time}ms to parse ${message.length} bytes, for call to ${call.classGuid}.${call.functionName}`));
|
|
264
307
|
}
|
|
265
|
-
lastReceivedSeqNum = call.seqNum;
|
|
266
308
|
|
|
267
309
|
let response: InternalReturnType;
|
|
268
310
|
try {
|
|
@@ -288,13 +330,24 @@ export async function createCallFactory(
|
|
|
288
330
|
let result: Buffer;
|
|
289
331
|
if (isNode() && call.compress && SocketFunction.compression?.type === "gzip") {
|
|
290
332
|
response.compressed = true;
|
|
291
|
-
result = Buffer.from(
|
|
333
|
+
result = Buffer.from(JSONLACKS.stringify(response));
|
|
292
334
|
result = await new Promise<Buffer>((resolve, reject) =>
|
|
293
335
|
gzip(result, (err, result) => err ? reject(err) : resolve(result))
|
|
294
336
|
);
|
|
295
337
|
result = Buffer.concat([new Uint8Array([0]), result]);
|
|
296
338
|
} else {
|
|
297
|
-
result = Buffer.from(
|
|
339
|
+
result = Buffer.from(JSONLACKS.stringify(response));
|
|
340
|
+
}
|
|
341
|
+
if (result.byteLength > SocketFunction.MAX_MESSAGE_SIZE * 1.5) {
|
|
342
|
+
response = {
|
|
343
|
+
isReturn: true,
|
|
344
|
+
result: undefined,
|
|
345
|
+
seqNum: call.seqNum,
|
|
346
|
+
error: new Error(`Response too large to send (${call.classGuid}.${call.functionName}, size: ${result.byteLength} > ${SocketFunction.MAX_MESSAGE_SIZE}). If you need to handle very large static data use some external service, such as Backblaze B2 or AWS S3. Or consider fragmenting data at an application level, because sending large data will cause large lag spikes for other clients using this server. Or, if absolutely required, set SocketFunction.MAX_MESSAGE_SIZE to a higher value.`).stack,
|
|
347
|
+
resultSize: resultSize,
|
|
348
|
+
compressed: false,
|
|
349
|
+
};
|
|
350
|
+
result = Buffer.from(JSONLACKS.stringify(response));
|
|
298
351
|
}
|
|
299
352
|
await send(result);
|
|
300
353
|
}
|
|
@@ -302,6 +355,8 @@ export async function createCallFactory(
|
|
|
302
355
|
}
|
|
303
356
|
throw new Error(`Unhandled data type ${typeof message}`);
|
|
304
357
|
} catch (e: any) {
|
|
358
|
+
debugbreak(1);
|
|
359
|
+
debugger;
|
|
305
360
|
console.error(e.stack);
|
|
306
361
|
}
|
|
307
362
|
}
|