socket-function 0.8.11 → 0.8.14

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 CHANGED
@@ -1,7 +1,7 @@
1
- import { SocketExposedInterface, CallContextType, SocketFunctionHook, SocketFunctionClientHook, SocketExposedShape, SocketRegistered, NetworkLocation, CallerContext, SocketExposedInterfaceClass, CallType } from "./SocketFunctionTypes";
1
+ import { SocketExposedInterface, CallContextType, SocketFunctionHook, SocketFunctionClientHook, SocketExposedShape, SocketRegistered, NetworkLocation, CallerContext, SocketExposedInterfaceClass, CallType, FullCallType } from "./SocketFunctionTypes";
2
2
  import { exposeClass, registerClass, registerGlobalClientHook, registerGlobalHook, runClientHooks } from "./src/callManager";
3
3
  import { SocketServerConfig, startSocketServer } from "./src/webSocketServer";
4
- import { getCallFactoryNodeId, getCreateCallFactoryLocation, getNetworkLocationHash } from "./src/nodeCache";
4
+ import { getCallFactoryFromNodeId, getCreateCallFactoryLocation, getNetworkLocationHash } from "./src/nodeCache";
5
5
  import { getCallProxy } from "./src/nodeProxy";
6
6
  import { Args } from "./src/types";
7
7
  import { setDefaultHTTPCall } from "./src/callHTTPHandler";
@@ -53,13 +53,15 @@ export class SocketFunction {
53
53
 
54
54
  registerClass(classGuid, instance as SocketExposedInterface, shape as any as SocketExposedShape);
55
55
 
56
- let nodeProxy = getCallProxy(classGuid, async (nodeId, functionName, args) => {
56
+ let nodeProxy = getCallProxy(classGuid, async (call) => {
57
+ let nodeId = call.nodeId;
58
+ let functionName = call.functionName;
57
59
  let time = Date.now();
58
60
  if (SocketFunction.logMessages) {
59
61
  console.log(`START\t\t\t${classGuid}.${functionName}`);
60
62
  }
61
63
  try {
62
- let callFactory = await getCallFactoryNodeId(nodeId);
64
+ let callFactory = await getCallFactoryFromNodeId(nodeId);
63
65
  if (!callFactory) {
64
66
  throw new Error(`Cannot reach node ${nodeId}. It might have been incorrect provided to us via another node, which should have provided us a NetworkLocation instead.`);
65
67
  }
@@ -69,12 +71,6 @@ export class SocketFunction {
69
71
  throw new Error(`Function ${functionName} is not in shape`);
70
72
  }
71
73
 
72
- let call: CallType = {
73
- classGuid,
74
- args,
75
- functionName,
76
- };
77
-
78
74
  let hookResult = await runClientHooks(call, shapeObj as SocketExposedShape[""]);
79
75
 
80
76
  if ("overrideResult" in hookResult) {
@@ -99,6 +95,20 @@ export class SocketFunction {
99
95
  return output as any;
100
96
  }
101
97
 
98
+ /** Gets HTTP call link */
99
+ public static async getHTTPCallLink(call: FullCallType): Promise<string> {
100
+ let factory = await getCallFactoryFromNodeId(call.nodeId);
101
+ if (!factory) {
102
+ throw new Error(`Cannot find call factory for nodeId, and so do not know where call location is. NodeId ${call.nodeId}`);
103
+ }
104
+ let location = factory.location;
105
+ let url = new URL(`https://${location.address}:${location.listeningPorts[0]}`);
106
+ url.searchParams.set("classGuid", call.classGuid);
107
+ url.searchParams.set("functionName", call.functionName);
108
+ url.searchParams.set("args", JSON.stringify(call.args));
109
+ return url.toString();
110
+ }
111
+
102
112
  /** Expose should be called before your mounting occurs. It mostly just exists to ensure you include the class type,
103
113
  * so the class type's module construction runs, which should trigger register. Otherwise you would have
104
114
  * to add additional imports to ensure the register call runs.
@@ -2,12 +2,20 @@ module.allowclient = true;
2
2
 
3
3
  import debugbreak from "debugbreak";
4
4
  import * as tls from "tls";
5
+ import { getCallObj } from "./src/nodeProxy";
6
+ import { Args } from "./src/types";
5
7
 
6
8
  export const socket = Symbol("socket");
7
9
 
8
10
  export type SocketExposedInterface = {
9
11
  [functionName: string]: (...args: any[]) => Promise<unknown>;
10
12
  };
13
+ export type SocketInternalInterface = {
14
+ [functionName: string]: {
15
+ [getCallObj]: (...args: any[]) => FullCallType;
16
+ (...args: any[]): Promise<unknown>;
17
+ }
18
+ }
11
19
  export type SocketExposedInterfaceClass = {
12
20
  //new(): SocketExposedInterface;
13
21
  new(): unknown;
@@ -28,6 +36,9 @@ export interface CallType {
28
36
  // To set a timeout on returns, you can set it in the server hook.
29
37
  reconnectTimeout?: number;
30
38
  }
39
+ export interface FullCallType extends CallType {
40
+ nodeId: string;
41
+ }
31
42
 
32
43
  export interface SocketFunctionHook<ExposedType extends SocketExposedInterface = SocketExposedInterface, CallContext extends CallContextType = CallContextType> {
33
44
  (config: HookContext<ExposedType, CallContext>): Promise<void>;
@@ -56,7 +67,11 @@ export interface SocketRegistered<ExposedType extends SocketExposedInterface = S
56
67
  nodes: {
57
68
  // NOTE: Don't pass around nodeId to other nodes, instead pass around NetworkLocation (which they
58
69
  // then turn into a nodeId, which they can then check permissions on themself).
59
- [nodeId: string]: ExposedType;
70
+ [nodeId: string]: {
71
+ [functionName in keyof ExposedType]: ExposedType[functionName] & {
72
+ [getCallObj]: (...args: Args<ExposedType[functionName]>) => FullCallType;
73
+ }
74
+ };
60
75
  };
61
76
  context: {
62
77
  // If undefined we are not synchronously in a call
@@ -6,7 +6,9 @@ export function promiseToObservable<T>(promise: Promise<T>): { value: T | undefi
6
6
  let error: unknown;
7
7
  let result: T | undefined;
8
8
 
9
- let isDoneTrigger = observable({ value: false });
9
+ let isDoneTrigger = observable({
10
+ value: false
11
+ });
10
12
  promise.then(
11
13
  r => {
12
14
  result = r;
@@ -37,18 +39,49 @@ export function asyncObservable<Output, Key>(maxCount: number, getValue: (key: K
37
39
  invalidate(key: Key): void;
38
40
  invalidateAll(): void;
39
41
  } {
40
- let cache = cacheLimited(maxCount, (keyJSON: string) => {
41
- let key = keyJSON ? JSON.parse(keyJSON) : keyJSON;
42
- // We call inside another function so that synchronous errors still get wrapped in the observable
43
- let value = (async () => getValue(key))();
44
- return promiseToObservable(value);
45
- });
42
+ // NOTE: Not very efficient (invalidates too much), but... makes triggering invalidations much faster/easier,
43
+ // making an invalidate depend on the total render time, not the total cached state size.
44
+ let invalidateSeqNum = observable({ seqNum: 1 }, undefined, { deep: false, proxy: false });
45
+ let startingCalculating = new Set<string>();
46
+ let values = new Map<string, { value: Output | undefined }>();
47
+
46
48
  get["invalidate"] = (key: Key) => {
47
- cache.invalidate(JSON.stringify(key));
49
+ let hash = JSON.stringify(key);
50
+ let value = values.get(hash);
51
+ if (!value) return;
52
+ values.delete(hash);
53
+ startingCalculating.delete(hash);
54
+ invalidateSeqNum.seqNum++;
55
+ };
56
+ get["invalidateAll"] = () => {
57
+ startingCalculating.clear();
58
+ values.clear();
59
+ invalidateSeqNum.seqNum++;
48
60
  };
49
- get["invalidateAll"] = () => cache.invalidateAll();
50
61
  function get(key: Key) {
51
- return cache(JSON.stringify(key)).value;
62
+ invalidateSeqNum.seqNum;
63
+ let hash = JSON.stringify(key);
64
+ let value = values.get(hash);
65
+ if (value) {
66
+ return value.value;
67
+ }
68
+ if (startingCalculating.has(hash)) {
69
+ throw new Error(`Cyclic access in cache`);
70
+ }
71
+ startingCalculating.add(hash);
72
+
73
+ // Not very efficient, but clearing the entire state is a lot easier to do then
74
+ // keep track of the order they are accessed (and it does make MOST accesses
75
+ // MUCH faster).
76
+ if (values.size >= maxCount) {
77
+ values.clear();
78
+ startingCalculating.clear();
79
+ }
80
+
81
+ // We call inside another function so that synchronous errors still get wrapped in the observable
82
+ value = promiseToObservable((async () => getValue(key))());
83
+ values.set(hash, value);
84
+ return value.value;
52
85
  }
53
86
  return get;
54
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.8.11",
3
+ "version": "0.8.14",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
package/src/caching.ts CHANGED
@@ -64,22 +64,10 @@ export function cacheLimited<Output, Key>(
64
64
  // calculating, keeping a consistent output can save (a considerable amount of) time in downstream caches.
65
65
  maxCount: number,
66
66
  getValue: (key: Key) => Output
67
- ): {
68
- (key: Key): Output;
69
- invalidate(key: Key): void;
70
- invalidateAll(): void;
71
- } {
67
+ ): (key: Key) => Output {
72
68
  let startingCalculating = new Set<Key>();
73
69
  let values = new Map<Key, Output>();
74
- get["invalidate"] = (key: Key) => {
75
- values.delete(key);
76
- startingCalculating.delete(key);
77
- };
78
- get["invalidateAll"] = () => {
79
- values.clear();
80
- startingCalculating.clear();
81
- };
82
- function get(input: Key) {
70
+ return (input) => {
83
71
  let key = input;
84
72
  if (values.has(key)) {
85
73
  return values.get(key) as any;
@@ -103,8 +91,7 @@ export function cacheLimited<Output, Key>(
103
91
  let value = getValue(input);
104
92
  values.set(key, value);
105
93
  return value;
106
- }
107
- return get;
94
+ };
108
95
  }
109
96
 
110
97
  export function cacheWeak<Output, Key extends object>(getValue: (key: Key) => Output): (key: Key) => Output {
package/src/nodeCache.ts CHANGED
@@ -87,6 +87,6 @@ export function getCreateCallFactoryLocation(location: NetworkLocation, tempNode
87
87
 
88
88
 
89
89
  // TODO: Give a special error if the nodeId has been seen, but is only one-way (from HTTP requests).
90
- export async function getCallFactoryNodeId(nodeId: string): Promise<CallFactory | undefined> {
90
+ export async function getCallFactoryFromNodeId(nodeId: string): Promise<CallFactory | undefined> {
91
91
  return await nodeCache.get(nodeId)?.callFactory;
92
92
  }
package/src/nodeProxy.ts CHANGED
@@ -1,29 +1,52 @@
1
1
  import { lazy } from "./caching";
2
- import { SocketExposedInterface } from "../SocketFunctionTypes";
2
+ import { FullCallType, SocketExposedInterface, SocketInternalInterface } from "../SocketFunctionTypes";
3
3
 
4
4
  type CallProxyType = {
5
- [controllerName: string]: SocketExposedInterface;
5
+ [nodeId: string]: SocketInternalInterface;
6
6
  };
7
7
 
8
+ export const getCallObj = Symbol.for("getCallObj");
9
+
8
10
  let proxyCache = new Map<string, CallProxyType>();
9
- export function getCallProxy(id: string, callback: (controllerName: string, functionName: string, args: unknown[]) => Promise<unknown>): CallProxyType {
11
+ export function getCallProxy(id: string, callback: (callType: FullCallType) => Promise<unknown>): CallProxyType {
10
12
  let value = proxyCache.get(id);
11
13
  if (!value) {
12
- let controllerCache = new Map<string, CallProxyType[""]>();
14
+ let nodeCache = new Map<string, CallProxyType[""]>();
13
15
  value = new Proxy(Object.create(null), {
14
- get(target, controllerName) {
15
- if (typeof controllerName !== "string") return undefined;
16
- let controller = controllerCache.get(controllerName);
17
- if (!controller) {
18
- controller = 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), {
19
21
  get(target, functionName) {
20
22
  if (typeof functionName !== "string") return undefined;
21
- return (...args: unknown[]) => callback(controllerName, functionName, args);
23
+ return Object.assign(
24
+ (...args: unknown[]) => {
25
+ let call: FullCallType = {
26
+ classGuid: id,
27
+ nodeId,
28
+ functionName,
29
+ args,
30
+ };
31
+ return callback(call);
32
+ },
33
+ {
34
+ [getCallObj]: (...args: unknown[]) => {
35
+ let call: FullCallType = {
36
+ classGuid: id,
37
+ nodeId,
38
+ functionName,
39
+ args,
40
+ };
41
+ return call;
42
+ }
43
+ }
44
+ );
22
45
  }
23
46
  }) as CallProxyType[""];
24
- controllerCache.set(controllerName, controller);
47
+ nodeCache.set(nodeId, nodeProxy);
25
48
  }
26
- return controller;
49
+ return nodeProxy;
27
50
  },
28
51
  }) as CallProxyType;
29
52
  proxyCache.set(id, value);
package/test/client.ts CHANGED
@@ -10,6 +10,7 @@ import { Test } from "./shared";
10
10
  import "../require/CSSShim";
11
11
  import "./client.css";
12
12
  import { isNode } from "../src/misc";
13
+ import { getCallObj } from "../src/nodeProxy";
13
14
 
14
15
  module.allowclient = true;
15
16
 
@@ -23,6 +24,8 @@ void main();
23
24
  async function main() {
24
25
  if (isNode()) return;
25
26
 
27
+ SocketFunction.rejectUnauthorized = false;
28
+
26
29
  SocketFunction.expose(Test);
27
30
 
28
31
  console.log("cool");
package/test/server.ts CHANGED
@@ -16,11 +16,14 @@ import "../require/CSSShim";
16
16
 
17
17
  // Import clientside files, so they can be whitelisted
18
18
  import "./client";
19
+ import { getCallObj } from "../src/nodeProxy";
19
20
 
20
21
 
21
22
  void main();
22
23
 
23
24
  async function main() {
25
+ SocketFunction.rejectUnauthorized = false;
26
+
24
27
  SocketFunction.expose(Test);
25
28
 
26
29
  RequireController._classGuid;