socket-function 0.131.0 → 0.133.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.
@@ -1,129 +1,129 @@
1
- /// <reference path="./require/RequireController.ts" />
2
-
3
- module.allowclient = true;
4
-
5
- import { SocketFunction } from "./SocketFunction";
6
- import { getCallObj } from "./src/nodeProxy";
7
- import { Args, MaybePromise } from "./src/types";
8
-
9
- export const socket = Symbol("socket");
10
-
11
- export type SocketExposedInterface = {
12
- [functionName: string]: (...args: any[]) => Promise<unknown>;
13
- };
14
- export type SocketInternalInterface = {
15
- [functionName: string]: {
16
- [getCallObj]: (...args: any[]) => FullCallType;
17
- (...args: any[]): Promise<unknown>;
18
- }
19
- }
20
- export type SocketExposedInterfaceClass = {
21
- //new(): SocketExposedInterface;
22
- new(): unknown;
23
- prototype: unknown;
24
- };
25
- export type FunctionFlags = {
26
- compress?: boolean;
27
-
28
- /** Indicates with the same input, we give the same output, forever,
29
- * independent of code changes. This only works for data storage.
30
- */
31
- dataImmutable?: boolean;
32
-
33
- /** Allows overriding SocketFunction.MAX_MESSAGE_SIZE for responses from this function. */
34
- responseLimit?: number;
35
- };
36
- export type SocketExposedShape<ExposedType extends SocketExposedInterface = SocketExposedInterface> = {
37
- [functionName in keyof ExposedType]?: FunctionFlags & {
38
- // NOTE: Due tohow register is called we can't use ExposedType[functionName] here,
39
- // because we didn'tt use the double call pattern. Maybe we will later,
40
- // but the type benefits are marginal. Args and overrideResult can be typed,
41
- // but 99% of the time those are used by generic helper functions anyways,
42
- // which only want unknowns anyways.
43
- hooks?: SocketFunctionHook[];
44
- clientHooks?: SocketFunctionClientHook[];
45
- noDefaultHooks?: boolean;
46
- /** BUG: I think this is broken if it is on the default hooks function? */
47
- noClientHooks?: boolean;
48
- };
49
- };
50
-
51
- export type FncType = (...args: any[]) => Promise<unknown>;
52
- export interface CallType<FncT extends FncType = FncType, FncName extends string = string> {
53
- classGuid: string;
54
- functionName: FncName;
55
- args: unknown[];
56
- }
57
- export interface FullCallType<FncT extends FncType = FncType, FncName extends string = string> extends CallType<FncT, FncName> {
58
- nodeId: string;
59
- }
60
-
61
- export interface SocketFunctionHook {
62
- (config: HookContext): MaybePromise<void>;
63
- /** NOTE: This is useful when we need a clientside hook to set up state specifically for our serverside hook. */
64
- clientHook?: SocketFunctionClientHook;
65
- }
66
- export type HookContext = {
67
- call: FullCallType;
68
- // If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
69
- // - It is important we continue evaluating hooks, in case some later hooks check permissions
70
- // and throw. We wouldn't want a caching layer to accidentally avoid a permissions check.
71
- overrideResult?: unknown;
72
- // Is called on a result, even if it is from overrideResult
73
- // Maybe further mutate overrideResult, or even add it
74
- onResult: ((result: unknown) => MaybePromise<void>)[];
75
- };
76
-
77
- export type ClientHookContext = {
78
- call: FullCallType;
79
- // If the result is overriden, we STOP evaluating hooks and do not perform the final call
80
- // - We stop evaluating hooks, because other hooks might end up making unnecessary calls,
81
- // which won't be needed, because we aren't calling the server. There is no security issue,
82
- // because the clientside checks are never security checks (how could they be, the client
83
- // can't authorize itself...)
84
- overrideResult?: unknown;
85
- // Is called on a result, even if it is from overrideResult
86
- onResult: ((result: unknown) => MaybePromise<void>)[];
87
- // IMPORTANT! This is a unique object per connection, reused within the connection. This
88
- // is allows it to be used as a cache key for connection related info (in a WeakMap).
89
- connectionId: { nodeId: string };
90
- };
91
- export interface SocketFunctionClientHook {
92
- (config: ClientHookContext): MaybePromise<void>;
93
- }
94
-
95
- export interface SocketRegisterType<ExposedType = any> {
96
- _classGuid: string;
97
- _internalType: ExposedType;
98
- }
99
-
100
- export interface SocketRegistered<ExposedType = any> {
101
- nodes: {
102
- // NOTE: Don't pass around nodeId to other nodes, instead pass around NetworkLocation (which they
103
- // then turn into a nodeId, which they can then check permissions on themself).
104
- [nodeId: string]: {
105
- [functionName in keyof ExposedType]: ExposedType[functionName] & {
106
- [getCallObj]: (...args: Args<ExposedType[functionName]>) => FullCallType;
107
- }
108
- };
109
- };
110
- _classGuid: string;
111
- _internalType: ExposedType;
112
- }
113
- export type ControllerPick<T extends SocketRegistered, K extends keyof T["_internalType"]> = (
114
- SocketRegistered<Pick<T["_internalType"], K>>
115
- );
116
- export type CallerContext = Readonly<CallerContextBase>;
117
- export type CallerContextBase = {
118
- // IMPORTANT! Do not pass nodeId to other nodes with the intention of having
119
- // them call functions directly using nodeId. Instead pass location, and have them use connect.
120
- // - nodeId will be unique per thread, so is only useful for temporary communcation. If you want
121
- // a more permanent identity, you must derive it from certInfo yourself.
122
- nodeId: string;
123
-
124
- // The nodeId they contacted. This is useful to determine their intention (otherwise
125
- // requests can be redirected to us and would accept them, even though they are being
126
- // blatantly MITMed).
127
- // IF they are the server, calling us back, then this will just be ""
128
- localNodeId: string;
1
+ /// <reference path="./require/RequireController.ts" />
2
+
3
+ module.allowclient = true;
4
+
5
+ import { SocketFunction } from "./SocketFunction";
6
+ import { getCallObj } from "./src/nodeProxy";
7
+ import { Args, MaybePromise } from "./src/types";
8
+
9
+ export const socket = Symbol("socket");
10
+
11
+ export type SocketExposedInterface = {
12
+ [functionName: string]: (...args: any[]) => Promise<unknown>;
13
+ };
14
+ export type SocketInternalInterface = {
15
+ [functionName: string]: {
16
+ [getCallObj]: (...args: any[]) => FullCallType;
17
+ (...args: any[]): Promise<unknown>;
18
+ }
19
+ }
20
+ export type SocketExposedInterfaceClass = {
21
+ //new(): SocketExposedInterface;
22
+ new(): unknown;
23
+ prototype: unknown;
24
+ };
25
+ export type FunctionFlags = {
26
+ compress?: boolean;
27
+
28
+ /** Indicates with the same input, we give the same output, forever,
29
+ * independent of code changes. This only works for data storage.
30
+ */
31
+ dataImmutable?: boolean;
32
+
33
+ /** Allows overriding SocketFunction.MAX_MESSAGE_SIZE for responses from this function. */
34
+ responseLimit?: number;
35
+ };
36
+ export type SocketExposedShape<ExposedType extends SocketExposedInterface = SocketExposedInterface> = {
37
+ [functionName in keyof ExposedType]?: FunctionFlags & {
38
+ // NOTE: Due tohow register is called we can't use ExposedType[functionName] here,
39
+ // because we didn'tt use the double call pattern. Maybe we will later,
40
+ // but the type benefits are marginal. Args and overrideResult can be typed,
41
+ // but 99% of the time those are used by generic helper functions anyways,
42
+ // which only want unknowns anyways.
43
+ hooks?: SocketFunctionHook[];
44
+ clientHooks?: SocketFunctionClientHook[];
45
+ noDefaultHooks?: boolean;
46
+ /** BUG: I think this is broken if it is on the default hooks function? */
47
+ noClientHooks?: boolean;
48
+ };
49
+ };
50
+
51
+ export type FncType = (...args: any[]) => Promise<unknown>;
52
+ export interface CallType<FncT extends FncType = FncType, FncName extends string = string> {
53
+ classGuid: string;
54
+ functionName: FncName;
55
+ args: unknown[];
56
+ }
57
+ export interface FullCallType<FncT extends FncType = FncType, FncName extends string = string> extends CallType<FncT, FncName> {
58
+ nodeId: string;
59
+ }
60
+
61
+ export interface SocketFunctionHook {
62
+ (config: HookContext): MaybePromise<void>;
63
+ /** NOTE: This is useful when we need a clientside hook to set up state specifically for our serverside hook. */
64
+ clientHook?: SocketFunctionClientHook;
65
+ }
66
+ export type HookContext = {
67
+ call: FullCallType;
68
+ // If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
69
+ // - It is important we continue evaluating hooks, in case some later hooks check permissions
70
+ // and throw. We wouldn't want a caching layer to accidentally avoid a permissions check.
71
+ overrideResult?: unknown;
72
+ // Is called on a result, even if it is from overrideResult
73
+ // Maybe further mutate overrideResult, or even add it
74
+ onResult: ((result: unknown) => MaybePromise<void>)[];
75
+ };
76
+
77
+ export type ClientHookContext = {
78
+ call: FullCallType;
79
+ // If the result is overriden, we STOP evaluating hooks and do not perform the final call
80
+ // - We stop evaluating hooks, because other hooks might end up making unnecessary calls,
81
+ // which won't be needed, because we aren't calling the server. There is no security issue,
82
+ // because the clientside checks are never security checks (how could they be, the client
83
+ // can't authorize itself...)
84
+ overrideResult?: unknown;
85
+ // Is called on a result, even if it is from overrideResult
86
+ onResult: ((result: unknown) => MaybePromise<void>)[];
87
+ // IMPORTANT! This is a unique object per connection, reused within the connection. This
88
+ // is allows it to be used as a cache key for connection related info (in a WeakMap).
89
+ connectionId: { nodeId: string };
90
+ };
91
+ export interface SocketFunctionClientHook {
92
+ (config: ClientHookContext): MaybePromise<void>;
93
+ }
94
+
95
+ export interface SocketRegisterType<ExposedType = any> {
96
+ _classGuid: string;
97
+ _internalType: ExposedType;
98
+ }
99
+
100
+ export interface SocketRegistered<ExposedType = any> {
101
+ nodes: {
102
+ // NOTE: Don't pass around nodeId to other nodes, instead pass around NetworkLocation (which they
103
+ // then turn into a nodeId, which they can then check permissions on themself).
104
+ [nodeId: string]: {
105
+ [functionName in keyof ExposedType]: ExposedType[functionName] & {
106
+ [getCallObj]: (...args: Args<ExposedType[functionName]>) => FullCallType;
107
+ }
108
+ };
109
+ };
110
+ _classGuid: string;
111
+ _internalType: ExposedType;
112
+ }
113
+ export type ControllerPick<T extends SocketRegistered, K extends keyof T["_internalType"]> = (
114
+ SocketRegistered<Pick<T["_internalType"], K>>
115
+ );
116
+ export type CallerContext = Readonly<CallerContextBase>;
117
+ export type CallerContextBase = {
118
+ // IMPORTANT! Do not pass nodeId to other nodes with the intention of having
119
+ // them call functions directly using nodeId. Instead pass location, and have them use connect.
120
+ // - nodeId will be unique per thread, so is only useful for temporary communcation. If you want
121
+ // a more permanent identity, you must derive it from certInfo yourself.
122
+ nodeId: string;
123
+
124
+ // The nodeId they contacted. This is useful to determine their intention (otherwise
125
+ // requests can be redirected to us and would accept them, even though they are being
126
+ // blatantly MITMed).
127
+ // IF they are the server, calling us back, then this will just be ""
128
+ localNodeId: string;
129
129
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.131.0",
3
+ "version": "0.133.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
@@ -9,7 +9,7 @@
9
9
  "cbor-x": "^1.6.0",
10
10
  "mobx": "^6.6.2",
11
11
  "pako": "^2.1.0",
12
- "preact": "10.11.3",
12
+ "preact": "10.24.0",
13
13
  "typedev": "^0.4.0",
14
14
  "typenode": "^5.12.0",
15
15
  "ws": "^8.17.1"
package/src/batching.ts CHANGED
@@ -248,18 +248,18 @@ export function runInParallel<T extends (...args: any[]) => Promise<any>>(
248
248
  return parallelCall as T;
249
249
  }
250
250
 
251
+ let pollingRunning = true;
252
+ let pendingPolls = new Set<Promise<unknown>>();
253
+
251
254
  export function runInfinitePoll(
252
255
  delayTime: number,
253
256
  fnc: () => Promise<void> | void
254
257
  ) {
255
258
  void (async () => {
256
- while (true) {
259
+ while (pollingRunning) {
257
260
  await delay(delayTime);
258
- try {
259
- await fnc();
260
- } catch (e: any) {
261
- 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}`);
262
- }
261
+ if (!pollingRunning) break;
262
+ await runPollFnc(fnc);
263
263
  }
264
264
  })();
265
265
  }
@@ -274,12 +274,28 @@ export async function runInfinitePollCallAtStart(
274
274
  void (async () => {
275
275
  while (true) {
276
276
  await delay(delayTime);
277
- try {
278
- await fnc();
279
- } catch (e: any) {
280
- console.error(`Error in infinite poll ${fnc.name || fnc.toString().slice(0, 100)} (continuing poll loop)\n${e.stack}`);
281
- }
277
+ if (!pollingRunning) break;
278
+ await runPollFnc(fnc);
282
279
  }
283
280
  })();
284
281
  }
282
+ }
283
+
284
+ async function runPollFnc(fnc: () => Promise<void> | void) {
285
+ let promise = (async () => {
286
+ try {
287
+ return await fnc();
288
+ } catch (e: any) {
289
+ console.error(`Error in infinite poll ${fnc.name || fnc.toString().slice(0, 100)} (continuing poll loop)\n${e.stack}`);
290
+ }
291
+ })();
292
+ pendingPolls.add(promise);
293
+ await promise;
294
+ pendingPolls.delete(promise);
295
+ }
296
+
297
+ /** Disables polling, called on shutdown. Blocks until all pending poll loops finish */
298
+ export async function shutdownPolling() {
299
+ pollingRunning = false;
300
+ await Promise.all(Array.from(pendingPolls));
285
301
  }
package/src/nodeProxy.ts CHANGED
@@ -1,59 +1,59 @@
1
- import { lazy } from "./caching";
2
- import { FullCallType, SocketExposedInterface, SocketInternalInterface } from "../SocketFunctionTypes";
3
-
4
- type CallProxyType = {
5
- [nodeId: string]: SocketInternalInterface;
6
- };
7
-
8
- export const getCallObj = Symbol.for("getCallObj");
9
-
10
- let proxyCache = new Map<string, CallProxyType>();
11
- export function getCallProxy(id: string, callback: (callType: FullCallType) => Promise<unknown>): CallProxyType {
12
- let value = proxyCache.get(id);
13
- if (!value) {
14
- let nodeCache = new Map<string, CallProxyType[""]>();
15
- value = new Proxy(Object.create(null), {
16
- get(target, nodeId) {
17
- if (typeof nodeId !== "string") return undefined;
18
- let nodeProxy = nodeCache.get(nodeId);
19
- if (!nodeProxy) {
20
- nodeProxy = new Proxy(Object.create(null), {
21
- get(target, functionName) {
22
- if (typeof functionName !== "string") return undefined;
23
- // Ignore "then" calls, as they happen when awaiting a callProxy (to check if it's
24
- // a promise), and we don't want to act like a promise (we don't want to call the
25
- // "then" api call).
26
- if (functionName === "then") return undefined;
27
- return Object.assign(
28
- (...args: unknown[]) => {
29
- let call: FullCallType = {
30
- classGuid: id,
31
- nodeId,
32
- functionName,
33
- args,
34
- };
35
- return callback(call);
36
- },
37
- {
38
- [getCallObj]: (...args: unknown[]) => {
39
- let call: FullCallType = {
40
- classGuid: id,
41
- nodeId,
42
- functionName,
43
- args,
44
- };
45
- return call;
46
- }
47
- }
48
- );
49
- }
50
- }) as CallProxyType[""];
51
- nodeCache.set(nodeId, nodeProxy);
52
- }
53
- return nodeProxy;
54
- },
55
- }) as CallProxyType;
56
- proxyCache.set(id, value);
57
- }
58
- return value;
1
+ import { lazy } from "./caching";
2
+ import { FullCallType, SocketExposedInterface, SocketInternalInterface } from "../SocketFunctionTypes";
3
+
4
+ type CallProxyType = {
5
+ [nodeId: string]: SocketInternalInterface;
6
+ };
7
+
8
+ export const getCallObj = Symbol.for("getCallObj");
9
+
10
+ let proxyCache = new Map<string, CallProxyType>();
11
+ export function getCallProxy(id: string, callback: (callType: FullCallType) => Promise<unknown>): CallProxyType {
12
+ let value = proxyCache.get(id);
13
+ if (!value) {
14
+ let nodeCache = new Map<string, CallProxyType[""]>();
15
+ value = new Proxy(Object.create(null), {
16
+ get(target, nodeId) {
17
+ if (typeof nodeId !== "string") return undefined;
18
+ let nodeProxy = nodeCache.get(nodeId);
19
+ if (!nodeProxy) {
20
+ nodeProxy = new Proxy(Object.create(null), {
21
+ get(target, functionName) {
22
+ if (typeof functionName !== "string") return undefined;
23
+ // Ignore "then" calls, as they happen when awaiting a callProxy (to check if it's
24
+ // a promise), and we don't want to act like a promise (we don't want to call the
25
+ // "then" api call).
26
+ if (functionName === "then") return undefined;
27
+ return Object.assign(
28
+ (...args: unknown[]) => {
29
+ let call: FullCallType = {
30
+ classGuid: id,
31
+ nodeId,
32
+ functionName,
33
+ args,
34
+ };
35
+ return callback(call);
36
+ },
37
+ {
38
+ [getCallObj]: (...args: unknown[]) => {
39
+ let call: FullCallType = {
40
+ classGuid: id,
41
+ nodeId,
42
+ functionName,
43
+ args,
44
+ };
45
+ return call;
46
+ }
47
+ }
48
+ );
49
+ }
50
+ }) as CallProxyType[""];
51
+ nodeCache.set(nodeId, nodeProxy);
52
+ }
53
+ return nodeProxy;
54
+ },
55
+ }) as CallProxyType;
56
+ proxyCache.set(id, value);
57
+ }
58
+ return value;
59
59
  }