socket-function 0.8.38 → 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 +7 -2
- package/package.json +2 -2
- package/require/RequireController.ts +8 -2
- package/require/require.js +11 -26
- package/src/CallFactory.ts +27 -5
- package/src/batching.ts +33 -0
- package/src/fixLargeNetworkCalls.ts +9 -0
- package/src/profiling/measure.ts +5 -1
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
|
|
|
@@ -76,7 +77,7 @@ export class SocketFunction {
|
|
|
76
77
|
return shape as any as SocketExposedShape;
|
|
77
78
|
});
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
void Promise.resolve().then(() => {
|
|
80
81
|
registerClass(classGuid, instance as SocketExposedInterface, getShape());
|
|
81
82
|
});
|
|
82
83
|
|
|
@@ -115,7 +116,7 @@ export class SocketFunction {
|
|
|
115
116
|
_classGuid: classGuid,
|
|
116
117
|
};
|
|
117
118
|
|
|
118
|
-
|
|
119
|
+
void Promise.resolve().then(() => {
|
|
119
120
|
let onMount = getDefaultHooks?.().onMount;
|
|
120
121
|
if (onMount) {
|
|
121
122
|
let callbacks = SocketFunction.onMountCallbacks.get(classGuid);
|
|
@@ -209,6 +210,10 @@ export class SocketFunction {
|
|
|
209
210
|
if (config.ip) {
|
|
210
211
|
this.mountedIP = config.ip;
|
|
211
212
|
}
|
|
213
|
+
|
|
214
|
+
// Wait for any additionals functions to expose themselves
|
|
215
|
+
await delay("immediate");
|
|
216
|
+
|
|
212
217
|
this.mountedNodeId = await startSocketServer(config);
|
|
213
218
|
this.hasMounted = true;
|
|
214
219
|
for (let classGuid of SocketFunction.exposedClasses) {
|
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",
|
|
@@ -10,7 +10,7 @@
|
|
|
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-
|
|
13
|
+
"typenode": "^4.9.4-c",
|
|
14
14
|
"ws": "^8.8.0"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
@@ -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
|
@@ -242,6 +242,15 @@
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
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
|
+
}
|
|
245
254
|
|
|
246
255
|
let exportsOverride = undefined;
|
|
247
256
|
if (resolvedPath === "NOTALLOWEDCLIENTSIDE" || !serializedModules[resolvedPath].allowclient) {
|
|
@@ -337,6 +346,7 @@
|
|
|
337
346
|
module.id = resolvedId;
|
|
338
347
|
module.filename = serializedModule?.filename;
|
|
339
348
|
module.exports = {};
|
|
349
|
+
module.exports.default = module.exports;
|
|
340
350
|
module.children = [];
|
|
341
351
|
|
|
342
352
|
module.load = load;
|
|
@@ -383,19 +393,6 @@
|
|
|
383
393
|
|
|
384
394
|
let dirname = module.filename.replace(/\\/g, "/").split("/").slice(0, -1).join("/");
|
|
385
395
|
|
|
386
|
-
var __createBinding = (Object.create ? (function (o, m, k, k2) {
|
|
387
|
-
if (k2 === undefined) k2 = k;
|
|
388
|
-
Object.defineProperty(o, k2, { enumerable: true, get: function () { return m[k]; } });
|
|
389
|
-
}) : (function (o, m, k, k2) {
|
|
390
|
-
if (k2 === undefined) k2 = k;
|
|
391
|
-
o[k2] = m[k];
|
|
392
|
-
}));
|
|
393
|
-
var __setModuleDefault = (Object.create ? (function (o, v) {
|
|
394
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
395
|
-
}) : function (o, v) {
|
|
396
|
-
o["default"] = v;
|
|
397
|
-
});
|
|
398
|
-
|
|
399
396
|
let time = Date.now();
|
|
400
397
|
currentModuleEvaluationStack.push(module.filename);
|
|
401
398
|
try {
|
|
@@ -405,21 +402,9 @@
|
|
|
405
402
|
// which checks for unloadedModule and returns undefined in that case.
|
|
406
403
|
__importStar(mod) {
|
|
407
404
|
if (mod[unloadedModule]) return undefined;
|
|
408
|
-
|
|
409
|
-
var result = {};
|
|
410
|
-
if (mod !== null && mod !== undefined) {
|
|
411
|
-
for (var k in mod) {
|
|
412
|
-
if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) {
|
|
413
|
-
__createBinding(result, mod, k);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
__setModuleDefault(result, mod);
|
|
418
|
-
return result;
|
|
405
|
+
return mod;
|
|
419
406
|
},
|
|
420
407
|
__importDefault(mod) {
|
|
421
|
-
// If typescript isn't going to complain about importing from a module with no default export,
|
|
422
|
-
// then we'll just change our implementation to work the same way as typescript types...
|
|
423
408
|
return mod.default ? mod : { default: mod };
|
|
424
409
|
},
|
|
425
410
|
},
|
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";
|
|
@@ -11,6 +11,7 @@ import debugbreak from "debugbreak";
|
|
|
11
11
|
import { lazy } from "./caching";
|
|
12
12
|
import { JSONLACKS } from "./JSONLACKS/JSONLACKS";
|
|
13
13
|
import { red } from "./formatting/logColors";
|
|
14
|
+
import { isSplitableArray } from "./fixLargeNetworkCalls";
|
|
14
15
|
|
|
15
16
|
const MIN_RETRY_DELAY = 1000;
|
|
16
17
|
|
|
@@ -115,6 +116,31 @@ export async function createCallFactory(
|
|
|
115
116
|
if (time > SocketFunction.WIRE_WARN_TIME) {
|
|
116
117
|
console.log(red(`Slow serialize, took ${time}ms to serialize ${data.byteLength} bytes. For ${call.classGuid}.${call.functionName}`));
|
|
117
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
|
+
|
|
118
144
|
let resultPromise = new Promise((resolve, reject) => {
|
|
119
145
|
let callback = (result: InternalReturnType) => {
|
|
120
146
|
if (SocketFunction.logMessages) {
|
|
@@ -130,10 +156,6 @@ export async function createCallFactory(
|
|
|
130
156
|
pendingCalls.set(seqNum, { callback, data, call: fullCall });
|
|
131
157
|
});
|
|
132
158
|
|
|
133
|
-
if (data.byteLength > SocketFunction.MAX_MESSAGE_SIZE * 1.5) {
|
|
134
|
-
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.`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
159
|
await send(data);
|
|
138
160
|
|
|
139
161
|
return await resultPromise;
|
package/src/batching.ts
CHANGED
|
@@ -91,4 +91,37 @@ export function runInSerial<T extends (...args: any[]) => Promise<any>>(fnc: T):
|
|
|
91
91
|
updateQueue[0]?.();
|
|
92
92
|
}
|
|
93
93
|
}) as T;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function runInfinitePoll(
|
|
97
|
+
delayTime: number,
|
|
98
|
+
fnc: () => Promise<void> | void
|
|
99
|
+
) {
|
|
100
|
+
void (async () => {
|
|
101
|
+
while (true) {
|
|
102
|
+
await delay(delayTime);
|
|
103
|
+
try {
|
|
104
|
+
await fnc();
|
|
105
|
+
} catch (e: any) {
|
|
106
|
+
console.error(`Error in infinite poll ${fnc.name || fnc.toString().slice(0, 100).split("\n").slice(0, 2).join("\n")} (continuing poll loop)\n${e.stack}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
})();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function runInfinitePollCallAtStart(
|
|
113
|
+
delayTime: number,
|
|
114
|
+
fnc: () => Promise<void> | void
|
|
115
|
+
) {
|
|
116
|
+
void (async () => {
|
|
117
|
+
while (true) {
|
|
118
|
+
await delay(delayTime);
|
|
119
|
+
try {
|
|
120
|
+
await fnc();
|
|
121
|
+
} catch (e: any) {
|
|
122
|
+
console.error(`Error in infinite poll ${fnc.name} (continuing poll loop)\n${e.stack}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
})();
|
|
126
|
+
return await fnc();
|
|
94
127
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const arrayIsSplitable = Symbol.for("arrayIsSplitable");
|
|
2
|
+
export function markArrayAsSplitable<T>(data: T[]): T[] {
|
|
3
|
+
(data as any)[arrayIsSplitable] = true;
|
|
4
|
+
return data;
|
|
5
|
+
}
|
|
6
|
+
export function isSplitableArray<T>(data: T): data is T & (unknown[]) {
|
|
7
|
+
if (!Array.isArray(data)) return false;
|
|
8
|
+
return (data as any)[arrayIsSplitable] === true;
|
|
9
|
+
}
|
package/src/profiling/measure.ts
CHANGED
|
@@ -12,6 +12,10 @@ export function enableMeasurements() {
|
|
|
12
12
|
}
|
|
13
13
|
measurementsEnabled = true;
|
|
14
14
|
}
|
|
15
|
+
/** NOTE: Must be called BEFORE anything else is imported! */
|
|
16
|
+
export function disableMeasurements() {
|
|
17
|
+
measurementsEnabled = false;
|
|
18
|
+
}
|
|
15
19
|
|
|
16
20
|
let functionsSkipped = 0;
|
|
17
21
|
|
|
@@ -209,7 +213,7 @@ interface ProfileEntry {
|
|
|
209
213
|
stillOpenCount: number;
|
|
210
214
|
}
|
|
211
215
|
|
|
212
|
-
let measurementsEnabled =
|
|
216
|
+
let measurementsEnabled = true;
|
|
213
217
|
|
|
214
218
|
let outstandingProfiles: MeasureProfile[] = [];
|
|
215
219
|
function recordOwnTime(ownTimeObj: OwnTimeObj) {
|