socket-function 0.8.34 → 0.8.36

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,10 +1,14 @@
1
+ /// <reference path="./require/RequireController.ts" />
2
+
1
3
  import { SocketExposedInterface, CallContextType, SocketFunctionHook, SocketFunctionClientHook, SocketExposedShape, SocketRegistered, CallerContext, FullCallType } from "./SocketFunctionTypes";
2
4
  import { exposeClass, registerClass, registerGlobalClientHook, registerGlobalHook, runClientHooks } from "./src/callManager";
3
5
  import { SocketServerConfig, startSocketServer } from "./src/webSocketServer";
4
6
  import { getCreateCallFactoryLocation, getNodeId, getNodeIdLocation } from "./src/nodeCache";
5
7
  import { getCallProxy } from "./src/nodeProxy";
6
- import { Args } from "./src/types";
8
+ import { Args, MaybePromise } from "./src/types";
7
9
  import { setDefaultHTTPCall } from "./src/callHTTPHandler";
10
+ import debugbreak from "debugbreak";
11
+ import { lazy } from "./src/caching";
8
12
 
9
13
  module.allowclient = true;
10
14
 
@@ -28,8 +32,12 @@ export class SocketFunction {
28
32
  type: "gzip";
29
33
  };
30
34
  public static httpETagCache = false;
31
- public static rejectUnauthorized = true;
32
35
 
36
+ private static onMountCallbacks = new Map<string, (() => MaybePromise<void>)[]>();
37
+ public static exposedClasses = new Set<string>();
38
+
39
+ // NOTE: We use callbacks we don't run into issues with cyclic dependencies
40
+ // (ex, using a hook in a controller where the hook also calls the controller).
33
41
  public static register<
34
42
  ClassInstance extends object,
35
43
  Shape extends SocketExposedShape<SocketExposedInterface, CallContext>,
@@ -37,20 +45,28 @@ export class SocketFunction {
37
45
  >(
38
46
  classGuid: string,
39
47
  instance: ClassInstance,
40
- shape: Shape,
41
- defaultHooks?: SocketExposedShape[""]
42
- ):
43
- (
44
- SocketRegistered<ExtractShape<ClassInstance, Shape>, CallContext>
45
- ) {
46
-
47
- for (let value of Object.values(shape)) {
48
- if (!value) continue;
49
- value.clientHooks = [...(defaultHooks?.clientHooks || []), ...(value.clientHooks || [])];
50
- value.hooks = [...(defaultHooks?.hooks || []), ...(value.hooks || [])];
51
- value.dataImmutable = defaultHooks?.dataImmutable ?? value.dataImmutable;
48
+ shapeFnc: () => Shape,
49
+ defaultHooksFnc?: () => SocketExposedShape[""] & {
50
+ onMount?: () => MaybePromise<void>;
52
51
  }
53
- registerClass(classGuid, instance as SocketExposedInterface, shape as any as SocketExposedShape);
52
+ ): SocketRegistered<ExtractShape<ClassInstance, Shape>, CallContext> {
53
+ let getDefaultHooks = defaultHooksFnc && lazy(defaultHooksFnc);
54
+ const getShape = lazy(() => {
55
+ let shape = shapeFnc();
56
+ let defaultHooks = getDefaultHooks?.();
57
+
58
+ for (let value of Object.values(shape)) {
59
+ if (!value) continue;
60
+ value.clientHooks = [...(defaultHooks?.clientHooks || []), ...(value.clientHooks || [])];
61
+ value.hooks = [...(defaultHooks?.hooks || []), ...(value.hooks || [])];
62
+ value.dataImmutable = defaultHooks?.dataImmutable ?? value.dataImmutable;
63
+ }
64
+ return shape as any as SocketExposedShape;
65
+ });
66
+
67
+ setImmediate(() => {
68
+ registerClass(classGuid, instance as SocketExposedInterface, getShape());
69
+ });
54
70
 
55
71
  let nodeProxy = getCallProxy(classGuid, async (call) => {
56
72
  let nodeId = call.nodeId;
@@ -62,7 +78,7 @@ export class SocketFunction {
62
78
  try {
63
79
  let callFactory = await getCreateCallFactoryLocation(nodeId, SocketFunction.mountedNodeId);
64
80
 
65
- let shapeObj = shape[functionName];
81
+ let shapeObj = getShape()[functionName];
66
82
  if (!shapeObj) {
67
83
  throw new Error(`Function ${functionName} is not in shape`);
68
84
  }
@@ -73,20 +89,6 @@ export class SocketFunction {
73
89
  return hookResult.overrideResult;
74
90
  }
75
91
 
76
- if (hookResult.callTimeout !== undefined) {
77
- let timeout = hookResult.callTimeout;
78
- let time = Date.now();
79
- let timeoutPromise = new Promise((resolve, reject) => {
80
- setTimeout(() => {
81
- reject(new Error(`Call timed out after ${Date.now() - time}ms`));
82
- }, timeout);
83
- });
84
- return await Promise.race([
85
- callFactory.performCall(call),
86
- timeoutPromise,
87
- ]);
88
- }
89
-
90
92
  return await callFactory.performCall(call);
91
93
  } finally {
92
94
  time = Date.now() - time;
@@ -102,6 +104,18 @@ export class SocketFunction {
102
104
  _classGuid: classGuid,
103
105
  };
104
106
 
107
+ setImmediate(() => {
108
+ let onMount = getDefaultHooks?.().onMount;
109
+ if (onMount) {
110
+ let callbacks = SocketFunction.onMountCallbacks.get(classGuid);
111
+ if (!callbacks) {
112
+ callbacks = [];
113
+ SocketFunction.onMountCallbacks.set(classGuid, callbacks);
114
+ }
115
+ callbacks.push(onMount);
116
+ }
117
+ });
118
+
105
119
  return output as any;
106
120
  }
107
121
 
@@ -126,14 +140,33 @@ export class SocketFunction {
126
140
  */
127
141
  public static expose(socketRegistered: SocketRegistered) {
128
142
  exposeClass(socketRegistered);
143
+ SocketFunction.exposedClasses.add(socketRegistered._classGuid);
144
+
145
+ if (this.hasMounted) {
146
+ let mountCallbacks = SocketFunction.onMountCallbacks.get(socketRegistered._classGuid);
147
+ for (let onMount of mountCallbacks || []) {
148
+ Promise.resolve(onMount()).catch(e => {
149
+ console.error("Error in onMount callback exposed after mount", e);
150
+ });
151
+ }
152
+ }
129
153
  }
130
154
 
131
155
  public static mountedNodeId: string = "NOTMOUNTED";
156
+ private static hasMounted = false;
132
157
  public static async mount(config: SocketServerConfig) {
133
158
  if (this.mountedNodeId !== "NOTMOUNTED") {
134
159
  throw new Error("SocketFunction already mounted, mounting twice in one thread is not allowed.");
135
160
  }
136
161
  this.mountedNodeId = await startSocketServer(config);
162
+ this.hasMounted = true;
163
+ for (let classGuid of SocketFunction.exposedClasses) {
164
+ let callbacks = SocketFunction.onMountCallbacks.get(classGuid);
165
+ if (!callbacks) continue;
166
+ for (let callback of callbacks) {
167
+ await callback();
168
+ }
169
+ }
137
170
  return this.mountedNodeId;
138
171
  }
139
172
 
@@ -1,11 +1,7 @@
1
+ /// <reference path="./require/RequireController.ts" />
2
+
1
3
  module.allowclient = true;
2
4
 
3
- import debugbreak from "debugbreak";
4
- import * as tls from "tls";
5
- import { SenderInterface } from "./src/CallFactory";
6
- import { isNode } from "./src/misc";
7
- import { CertInfo, getNodeIdFromCert } from "./src/nodeAuthentication";
8
- import { getClientNodeId } from "./src/nodeCache";
9
5
  import { getCallObj } from "./src/nodeProxy";
10
6
  import { Args, MaybePromise } from "./src/types";
11
7
 
@@ -40,9 +36,6 @@ export interface CallType {
40
36
  classGuid: string;
41
37
  functionName: string;
42
38
  args: unknown[];
43
- // NOTE: When making calls this needs to be set in the client hook.
44
- // To set a timeout on returns, you can set it in the server hook.
45
- reconnectTimeout?: number;
46
39
  }
47
40
  export interface FullCallType extends CallType {
48
41
  nodeId: string;
@@ -50,20 +43,18 @@ export interface FullCallType extends CallType {
50
43
 
51
44
  export interface SocketFunctionHook<ExposedType extends SocketExposedInterface = SocketExposedInterface, CallContext extends CallContextType = CallContextType> {
52
45
  (config: HookContext<ExposedType, CallContext>): MaybePromise<void>;
46
+ /** NOTE: This is useful when we need a clientside hook to set up state specifically for our serverside hook. */
47
+ clientHook?: SocketFunctionClientHook<ExposedType, CallContext>;
53
48
  }
54
49
  export type HookContext<ExposedType extends SocketExposedInterface = SocketExposedInterface, CallContext extends CallContextType = CallContextType> = {
55
- call: CallType;
50
+ call: FullCallType;
56
51
  context: SocketRegistered<ExposedType, CallContext>["context"];
57
52
  // If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
58
53
  overrideResult?: unknown;
59
54
  };
60
55
 
61
56
  export type ClientHookContext<ExposedType extends SocketExposedInterface = SocketExposedInterface, CallContext extends CallContextType = CallContextType> = {
62
- call: CallType;
63
- /** If the calls takes longer than this (for ANY reason), we return with an error.
64
- * - Different from reconnectTimeout, which only errors if we lose the connection.
65
- */
66
- callTimeout?: number;
57
+ call: FullCallType;
67
58
  // If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
68
59
  overrideResult?: unknown;
69
60
  };
@@ -101,15 +92,6 @@ export type CallerContextBase = {
101
92
  // a more permanent identity, you must derive it from certInfo yourself.
102
93
  nodeId: string;
103
94
 
104
- /** Gives further info on the node. When we set this, we always make sure it has a verified
105
- * issuer. It may be set by app code, which should make sure the issuer is verified (not
106
- * necessarily by the machine, but just in some sense, 'verified', to secure the common name
107
- * of the cert and prevent anyone from using the same common name as someone else).
108
- * IF set, is directly used to derive nodeId (by nodeAuthentication.ts)
109
- */
110
- certInfo: CertInfo | undefined;
111
- updateCertInfo?: (certInfo: CertInfo, callbackPort: number | undefined) => void;
112
-
113
95
  // The nodeId they contacted. This is useful to determine their intention (otherwise
114
96
  // requests can be redirected to us and would accept them, even though they are being
115
97
  // blatantly MITMed).
@@ -64,8 +64,8 @@ class HotReloadControllerBase {
64
64
  export const HotReloadController = SocketFunction.register(
65
65
  "HotReloadController-032b2250-3aac-4187-8c95-75412742b8f5",
66
66
  new HotReloadControllerBase(),
67
- {
67
+ () => ({
68
68
  watchFiles: {},
69
69
  fileUpdated: {}
70
- }
70
+ })
71
71
  );
package/package.json CHANGED
@@ -1,16 +1,11 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.8.34",
3
+ "version": "0.8.36",
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",
7
7
  "dependencies": {
8
- "@types/cookie": "^0.5.1",
9
- "@types/node": "^18.0.0",
10
- "@types/node-forge": "^1.3.1",
11
- "@types/ws": "^8.5.3",
12
8
  "cookie": "^0.5.0",
13
- "debugbreak": "^0.6.5",
14
9
  "mobx": "^6.6.2",
15
10
  "node-forge": "https://github.com/sliftist/forge#name",
16
11
  "preact": "^10.10.6",
@@ -22,6 +17,10 @@
22
17
  "type": "yarn tsc --noEmit"
23
18
  },
24
19
  "devDependencies": {
25
- "typedev": "^0.1.0"
20
+ "@types/cookie": "^0.5.1",
21
+ "@types/node-forge": "^1.3.1",
22
+ "@types/ws": "^8.5.3",
23
+ "debugbreak": "^0.6.5",
24
+ "typedev": "^0.1.1"
26
25
  }
27
26
  }
@@ -1,3 +1,4 @@
1
+ /// <reference path=".//RequireController.ts" />
1
2
  import debugbreak from "debugbreak";
2
3
  import { compileTransformBefore } from "typenode";
3
4
 
@@ -1,3 +1,4 @@
1
+ /// <reference path="../../typenode/index.d.ts" />
1
2
  import debugbreak from "debugbreak";
2
3
  import fs from "fs";
3
4
  import { SocketFunction } from "../SocketFunction";
@@ -9,7 +10,7 @@ module.allowclient = true;
9
10
  declare global {
10
11
  namespace NodeJS {
11
12
  interface Module {
12
- /** Indiciates the module is allowed clientside. */
13
+ /** Indicates the module is allowed clientside. */
13
14
  allowclient?: boolean;
14
15
 
15
16
  /** Indicates the module is definitely not allowed clientside */
@@ -237,10 +238,10 @@ export function setRequireBootRequire(path: string) {
237
238
  export const RequireController = SocketFunction.register(
238
239
  "RequireController-e2f811f3-14b8-4759-b0d6-73f14516cf1d",
239
240
  baseController,
240
- {
241
+ () => ({
241
242
  getModules: {},
242
243
  requireHTML: {},
243
244
  bufferJS: {},
244
245
  requireJS: {},
245
- }
246
+ })
246
247
  );
@@ -1,3 +1,4 @@
1
+ /// <reference path="./RequireController.ts" />
1
2
  module.allowclient = true;
2
3
 
3
4
  /**
@@ -1,16 +1,16 @@
1
- import { CallerContext, CallerContextBase, CallType } from "../SocketFunctionTypes";
1
+ import { CallerContext, CallerContextBase, CallType, FullCallType } from "../SocketFunctionTypes";
2
2
  import * as ws from "ws";
3
3
  import { performLocalCall } from "./callManager";
4
4
  import { convertErrorStackToError, formatNumberSuffixed, isNode } from "./misc";
5
- import { createWebsocketFactory, getNodeIdFromCert, getTLSSocket } from "./nodeAuthentication";
5
+ import { createWebsocketFactory, getTLSSocket } from "./websocketFactory";
6
6
  import { SocketFunction } from "../SocketFunction";
7
7
  import { gzip } from "zlib";
8
8
  import * as tls from "tls";
9
9
  import { getClientNodeId, getNodeIdLocation, registerNodeClient } from "./nodeCache";
10
+ import debugbreak from "debugbreak";
11
+ import { lazy } from "./caching";
10
12
 
11
- const retryInterval = 2000;
12
-
13
- type InternalCallType = CallType & {
13
+ type InternalCallType = FullCallType & {
14
14
  seqNum: number;
15
15
  isReturn: false;
16
16
  compress: boolean;
@@ -45,6 +45,8 @@ export interface SenderInterface {
45
45
  addEventListener(event: "close", listener: () => void): void;
46
46
  addEventListener(event: "error", listener: (err: { message: string }) => void): void;
47
47
  addEventListener(event: "message", listener: (data: ws.RawData | ws.MessageEvent | string) => void): void;
48
+
49
+ readyState: number;
48
50
  }
49
51
 
50
52
  export async function createCallFactory(
@@ -55,19 +57,13 @@ export async function createCallFactory(
55
57
  let niceConnectionName = nodeId;
56
58
 
57
59
  const createWebsocket = createWebsocketFactory();
58
-
59
- let retriesEnabled = !!getNodeIdLocation(nodeId);
60
+ const registerOnce = lazy(() => registerNodeClient(callFactory));
60
61
 
61
62
  let lastReceivedSeqNum = 0;
62
63
 
63
- let reconnectingPromise: Promise<void> | undefined;
64
- let reconnectAttempts = 0;
65
-
66
-
67
64
  let pendingCalls: Map<number, {
68
65
  data: Buffer;
69
66
  call: InternalCallType;
70
- reconnectTimeout: number | undefined;
71
67
  callback: (resultJSON: InternalReturnType) => void;
72
68
  }> = new Map();
73
69
  // NOTE: It is important to make this as random as possible, to prevent
@@ -77,16 +73,7 @@ export async function createCallFactory(
77
73
 
78
74
  let callerContext: CallerContextBase = {
79
75
  nodeId,
80
- localNodeId,
81
- certInfo: webSocketBase?.socket?.getPeerCertificate(true),
82
- updateCertInfo: (certRaw, port) => {
83
- let nodeId = getNodeIdFromCert(certRaw, port);
84
- if (!nodeId) {
85
- return;
86
- }
87
- callerContext.nodeId = nodeId;
88
- callerContext.certInfo = certRaw;
89
- }
76
+ localNodeId
90
77
  };
91
78
 
92
79
  let callFactory: CallFactory = {
@@ -99,6 +86,7 @@ export async function createCallFactory(
99
86
 
100
87
  let seqNum = nextSeqNum++;
101
88
  let fullCall: InternalCallType = {
89
+ nodeId,
102
90
  isReturn: false,
103
91
  args: call.args,
104
92
  classGuid: call.classGuid,
@@ -119,144 +107,78 @@ export async function createCallFactory(
119
107
  resolve(result.result);
120
108
  }
121
109
  };
122
- pendingCalls.set(seqNum, { callback, data, call: fullCall, reconnectTimeout: call.reconnectTimeout });
110
+ pendingCalls.set(seqNum, { callback, data, call: fullCall });
123
111
  });
124
112
 
125
- await sendWithRetry(call.reconnectTimeout, data);
113
+ await send(data);
126
114
 
127
115
  return await resultPromise;
128
116
  }
129
117
  };
130
118
 
131
- let webSocket!: SenderInterface;
132
- if (!webSocketBase) {
133
- await tryToReconnect();
134
- } else {
135
- webSocket = webSocketBase;
136
- setupWebsocket(webSocketBase);
119
+ let webSocketPromise: Promise<SenderInterface> | undefined;
120
+ if (webSocketBase) {
121
+ webSocketPromise = Promise.resolve(webSocketBase);
122
+ await initializeWebsocket(webSocketBase);
137
123
  }
138
124
 
139
- niceConnectionName = `${niceConnectionName} (${callerContext.nodeId})`;
140
-
141
- async function sendWithRetry(reconnectTimeout: number | undefined, data: Buffer) {
142
- if (!retriesEnabled) {
143
- webSocket.send(data);
144
- return;
145
- }
146
-
147
- while (true) {
148
- if (reconnectingPromise) {
149
- if (reconnectTimeout) {
150
- await Promise.race([
151
- reconnectingPromise,
152
- new Promise<SenderInterface>(resolve =>
153
- setTimeout(() => {
154
- retriesEnabled = false;
155
- resolve(webSocket);
156
- }, reconnectTimeout)
157
- )
158
- ]);
159
- } else {
160
- await reconnectingPromise;
161
- }
162
- }
163
-
164
- if (!retriesEnabled) {
165
- webSocket.send(data);
166
- break;
167
- }
168
-
169
- try {
170
- webSocket.send(data);
171
- break;
172
- } catch (e) {
173
- // Ignore errors, as we will catch them synchronously in the next loop.
174
- void (tryToReconnect());
125
+ async function initializeWebsocket(newWebSocket: SenderInterface) {
126
+ registerOnce();
127
+
128
+ function onClose(error: string) {
129
+ webSocketPromise = undefined;
130
+ for (let [key, call] of pendingCalls) {
131
+ pendingCalls.delete(key);
132
+ call.callback({
133
+ isReturn: true,
134
+ result: undefined,
135
+ error: error,
136
+ seqNum: call.call.seqNum,
137
+ resultSize: 0,
138
+ compressed: false,
139
+ });
175
140
  }
176
141
  }
177
- }
178
- function tryToReconnect(): Promise<void> {
179
- if (reconnectingPromise) return reconnectingPromise;
180
- return reconnectingPromise = (async () => {
181
- while (true) {
182
- if (!retriesEnabled) {
183
- callFactory.closedForever = true;
184
- console.log(`Cannot reconnect to ${niceConnectionName}, aborting pendingCalls: ${pendingCalls.size}`);
185
- for (let call of pendingCalls.values()) {
186
- call.callback({
187
- isReturn: true,
188
- result: undefined,
189
- error: `Connection lost to ${niceConnectionName}`,
190
- seqNum: call.call.seqNum,
191
- resultSize: 0,
192
- compressed: false,
193
- });
194
- }
195
- return;
196
- }
197
-
198
- let newWebSocket = createWebsocket(nodeId);
199
-
200
- let connectError = await new Promise<string | undefined>(resolve => {
201
- newWebSocket.addEventListener("open", () => {
202
- resolve(undefined);
203
- });
204
- newWebSocket.addEventListener("close", () => {
205
- resolve("Connection closed for non-error reason?");
206
- });
207
- newWebSocket.addEventListener("error", e => {
208
- resolve(String(e.message));
209
- });
210
- });
211
142
 
212
- setupWebsocket(newWebSocket);
213
-
214
- if (!connectError) {
215
- console.log(`Reconnected to ${niceConnectionName}`);
143
+ newWebSocket.addEventListener("error", e => {
144
+ // NOTE: No more logging, as we throw, so the caller should be logging the
145
+ // error (or swallowing it, if that is what it wants to do).
146
+ //console.log(`Websocket error for ${niceConnectionName}`, e.message);
147
+ onClose(`Connection error for ${niceConnectionName}: ${e.message}`);
148
+ });
216
149
 
217
- // I'm not sure if we should clear reconnectAttempts? Maybe if we eventually have a max reconnectAttempts?
218
- //reconnectAttempts = 0;
219
- reconnectingPromise = undefined;
150
+ newWebSocket.addEventListener("close", async () => {
151
+ //console.log(`Websocket closed ${niceConnectionName}`);
152
+ onClose(`Connection closed to ${niceConnectionName}`);
153
+ });
220
154
 
221
- webSocket = newWebSocket;
155
+ newWebSocket.addEventListener("message", onMessage);
222
156
 
223
- for (let call of pendingCalls.values()) {
224
- sendWithRetry(call.reconnectTimeout, call.data).catch(e => {
225
- call.callback({
226
- isReturn: true,
227
- result: undefined,
228
- error: String(e),
229
- seqNum: call.call.seqNum,
230
- resultSize: 0,
231
- compressed: false,
232
- });
233
- });
234
- }
235
- return;
236
- }
237
157
 
238
- reconnectAttempts++;
239
- console.error(`Connection retry to ${niceConnectionName} failed (attempt ${reconnectAttempts}), retrying in ${retryInterval}ms, error: ${JSON.stringify(connectError)}`);
240
- await new Promise(resolve => setTimeout(resolve, retryInterval));
241
- }
242
- })();
158
+ if (newWebSocket.readyState === 0 /* CONNECTING */) {
159
+ await new Promise<void>(resolve => {
160
+ newWebSocket.addEventListener("open", () => {
161
+ console.log(`Connection established to ${niceConnectionName}`);
162
+ resolve();
163
+ });
164
+ newWebSocket.addEventListener("close", () => resolve());
165
+ newWebSocket.addEventListener("error", () => resolve());
166
+ });
167
+ } else if (newWebSocket.readyState !== 1 /* OPEN */) {
168
+ onClose(`Websocket received in closed state`);
169
+ }
243
170
  }
244
171
 
245
- function setupWebsocket(webSocket: SenderInterface) {
246
- registerNodeClient(callFactory);
247
-
248
- webSocket.addEventListener("error", e => {
249
- console.log(`Websocket error for ${niceConnectionName}`, e);
250
- });
251
-
252
- webSocket.addEventListener("close", async () => {
253
- console.log(`Websocket closed ${niceConnectionName}`);
254
- if (retriesEnabled) {
255
- await tryToReconnect();
256
- }
257
- });
172
+ async function send(data: Buffer) {
173
+ webSocketPromise = webSocketPromise || tryToReconnect();
174
+ let webSocket = await webSocketPromise;
175
+ webSocket.send(data);
176
+ }
177
+ async function tryToReconnect(): Promise<SenderInterface> {
178
+ let newWebSocket = createWebsocket(nodeId);
179
+ await initializeWebsocket(newWebSocket);
258
180
 
259
- webSocket.addEventListener("message", onMessage);
181
+ return newWebSocket;
260
182
  }
261
183
 
262
184
 
@@ -336,7 +258,7 @@ export async function createCallFactory(
336
258
  } else {
337
259
  result = Buffer.from(JSON.stringify(response));
338
260
  }
339
- await sendWithRetry(call.reconnectTimeout, result);
261
+ await send(result);
340
262
  }
341
263
  return;
342
264
  }
package/src/caching.ts CHANGED
@@ -33,6 +33,7 @@ export function cacheEmptyArray<T>(array: T[]): T[] {
33
33
 
34
34
  export function cache<Output, Key>(getValue: (key: Key) => Output): {
35
35
  (key: Key): Output;
36
+ clear(key: Key): void;
36
37
  } {
37
38
  let startingCalculating = new Set<Key>();
38
39
  let values = new Map<Key, Output>();
@@ -51,6 +52,9 @@ export function cache<Output, Key>(getValue: (key: Key) => Output): {
51
52
  values.set(key, value);
52
53
  return value;
53
54
  }
55
+ cache.clear = (key: Key) => {
56
+ values.delete(key);
57
+ };
54
58
  return cache;
55
59
  }
56
60
 
@@ -1,6 +1,6 @@
1
1
  import http from "http";
2
2
  import tls from "tls";
3
- import { CallerContext, CallType } from "../SocketFunctionTypes";
3
+ import { CallerContext, CallType, FullCallType } from "../SocketFunctionTypes";
4
4
  import { isDataImmutable, performLocalCall } from "./callManager";
5
5
  import { SocketFunction } from "../SocketFunction";
6
6
  import { gzip } from "zlib";
@@ -79,7 +79,6 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
79
79
 
80
80
  let caller: CallerContext = {
81
81
  nodeId,
82
- certInfo: undefined,
83
82
  localNodeId,
84
83
  };
85
84
 
@@ -112,7 +111,8 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
112
111
  args = JSON.parse(payload.toString())["args"] as unknown[];
113
112
  }
114
113
 
115
- let call: CallType = {
114
+ let call: FullCallType = {
115
+ nodeId,
116
116
  classGuid,
117
117
  functionName,
118
118
  args,
@@ -1,5 +1,7 @@
1
- import { CallContextType, CallerContext, CallType, ClientHookContext, HookContext, SocketExposedInterface, SocketExposedInterfaceClass, SocketExposedShape, SocketFunctionClientHook, SocketFunctionHook, SocketRegistered } from "../SocketFunctionTypes";
1
+ import { CallContextType, CallerContext, CallType, ClientHookContext, FullCallType, HookContext, SocketExposedInterface, SocketExposedInterfaceClass, SocketExposedShape, SocketFunctionClientHook, SocketFunctionHook, SocketRegistered } from "../SocketFunctionTypes";
2
2
  import { _setSocketContext } from "../SocketFunction";
3
+ import { isNode } from "./misc";
4
+ import debugbreak from "debugbreak";
3
5
 
4
6
  let classes: {
5
7
  [classGuid: string]: {
@@ -14,7 +16,7 @@ let globalClientHooks: SocketFunctionClientHook[] = [];
14
16
 
15
17
  export async function performLocalCall(
16
18
  config: {
17
- call: CallType;
19
+ call: FullCallType;
18
20
  caller: CallerContext;
19
21
  }
20
22
  ): Promise<unknown> {
@@ -92,11 +94,21 @@ export function unregisterGlobalClientHook(hook: SocketFunctionClientHook) {
92
94
  }
93
95
 
94
96
  export async function runClientHooks(
95
- callType: CallType,
97
+ callType: FullCallType,
96
98
  hooks: SocketExposedShape[""],
97
99
  ): Promise<ClientHookContext> {
98
100
  let context: ClientHookContext = { call: callType };
99
- for (let hook of globalClientHooks.concat(hooks.clientHooks || [])) {
101
+
102
+ let clientHooks = (
103
+ globalClientHooks
104
+ .concat(hooks.clientHooks || [])
105
+ );
106
+ for (let otherClientHook of globalHooks.concat(hooks.hooks || []).map(x => x.clientHook)) {
107
+ if (otherClientHook) {
108
+ clientHooks.push(otherClientHook);
109
+ }
110
+ }
111
+ for (let hook of clientHooks) {
100
112
  await hook(context);
101
113
  if ("overrideResult" in context) {
102
114
  break;
@@ -106,7 +118,7 @@ export async function runClientHooks(
106
118
  }
107
119
 
108
120
  async function runServerHooks(
109
- callType: CallType,
121
+ callType: FullCallType,
110
122
  context: SocketRegistered["context"],
111
123
  hooks: SocketExposedShape[""],
112
124
  ): Promise<HookContext> {
package/src/certStore.ts CHANGED
@@ -1,50 +1,24 @@
1
- import * as os from "os";
2
- import * as fs from "fs/promises";
3
- import * as fsSync from "fs";
4
- import * as child_process from "child_process";
5
1
  import * as tls from "tls";
6
- import { SocketFunction } from "../SocketFunction";
7
- import { isNode, isNodeTrue, sha256Hash } from "./misc";
8
- import { lazy } from "./caching";
9
2
 
10
3
  let trustedCerts = new Set<string>();
11
- let loadedTrustedCerts = false;
12
4
  let watchCallbacks = new Set<(certs: string[]) => void>();
13
5
 
14
- let storePath = isNodeTrue() && process.argv[1].replaceAll("\\", "/").split("/").slice(0, -1).join("/") + "/certstore/";
15
- if (isNode()) {
16
- if (!fsSync.existsSync(storePath)) {
17
- fsSync.mkdirSync(storePath);
18
- }
19
- }
20
-
21
6
  /** Must be populated before the server starts */
22
- export async function trustUserCertificate(cert: string) {
7
+ export function trustCertificate(cert: string | Buffer) {
8
+ cert = cert.toString();
23
9
  if (trustedCerts.has(cert)) return;
24
10
  trustedCerts.add(cert);
25
- await fs.writeFile(storePath + sha256Hash(Buffer.from(cert)) + ".cer", cert);
26
- let certs = getTrustedUserCertificates();
11
+ let certs = getTrustedCertificates();
27
12
  for (let callback of watchCallbacks) {
28
13
  callback(certs);
29
14
  }
30
15
  }
31
- export const loadTrustedUserCertificates = lazy(async () => {
32
- let files = await fs.readdir(storePath);
33
- for (let file of files) {
34
- let cert = await fs.readFile(storePath + file, "utf8");
35
- trustedCerts.add(cert);
36
- }
37
- loadedTrustedCerts = true;
38
- });
39
- export function getTrustedUserCertificates(): string[] {
40
- if (!loadedTrustedCerts) {
41
- throw new Error("Must call loadTrustedUserCertificates (and await it) before calling getTrustedUserCertificates");
42
- }
43
- return Array.from(trustedCerts);
16
+ export function getTrustedCertificates(): string[] {
17
+ return tls.rootCertificates.concat(Array.from(trustedCerts));
44
18
  }
45
19
 
46
- export function watchUserCertificates(callback: (certs: string[]) => void) {
20
+ export function watchTrustedCertificates(callback: (certs: string[]) => void) {
47
21
  watchCallbacks.add(callback);
48
- callback(getTrustedUserCertificates());
22
+ callback(getTrustedCertificates());
49
23
  return () => watchCallbacks.delete(callback);
50
24
  }
package/src/misc.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import * as crypto from "crypto";
2
+ import { MaybePromise } from "./types";
3
+
4
+ export type Watchable<T> = (callback: (value: T) => void) => MaybePromise<void>;
2
5
 
3
6
  export function convertErrorStackToError(error: string): Error {
4
7
  let errorObj = new Error();
package/src/nodeCache.ts CHANGED
@@ -19,11 +19,21 @@ export function getNodeId(domain: string, port: number): string {
19
19
 
20
20
  /** A nodeId not available for reconnecting. */
21
21
  export function getClientNodeId(address: string): string {
22
- return `client_${address}:${Date.now()}:${Math.random()}`;
22
+ return `client:${address}:${Date.now()}:${Math.random()}`;
23
+ }
24
+ /** Will always be available, even if getNodeIdLocation is not (as we don't always have the port,
25
+ * but we should always have an address).
26
+ * - 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
+ * */
28
+ export function getNodeIdIP(nodeId: string): string {
29
+ if (nodeId.startsWith("client:")) {
30
+ return nodeId.split(":")[1];
31
+ }
32
+ return getNodeIdLocation(nodeId)!.address;
23
33
  }
24
34
 
25
35
  export function getNodeIdLocation(nodeId: string): { address: string, port: number; } | undefined {
26
- if (nodeId.startsWith("client_")) {
36
+ if (nodeId.startsWith("client:")) {
27
37
  return undefined;
28
38
  }
29
39
  let [address, port] = nodeId.split(":");
@@ -3,28 +3,33 @@ import http from "http";
3
3
  import net from "net";
4
4
  import tls from "tls";
5
5
  import * as ws from "ws";
6
- import { getCertKeyPair, getNodeIdFromCert } from "./nodeAuthentication";
7
6
  import { getNodeIdsFromRequest, httpCallHandler } from "./callHTTPHandler";
8
7
  import { SocketFunction } from "../SocketFunction";
9
- import { getTrustedUserCertificates, loadTrustedUserCertificates, watchUserCertificates } from "./certStore";
8
+ import { getTrustedCertificates, watchTrustedCertificates } from "./certStore";
10
9
  import { createCallFactory } from "./CallFactory";
11
10
  import { parseSNIExtension, parseTLSHello, SNIType } from "./tlsParsing";
12
11
  import debugbreak from "debugbreak";
12
+ import { getNodeId } from "./nodeCache";
13
+ import crypto from "crypto";
14
+ import { Watchable } from "./misc";
13
15
 
14
16
  export type SocketServerConfig = (
15
17
  https.ServerOptions & {
18
+ key: string | Buffer;
19
+ cert: string | Buffer;
20
+
16
21
  port: number;
22
+ /** You can also set `port: 0` if you don't care what port you want at all. */
23
+ useAvailablePortIfPortInUse?: boolean;
17
24
 
18
25
  // public sets ip to "0.0.0.0", otherwise it defaults to "127.0.0.1", which
19
26
  // causes the server to only accept local connections.
20
27
  public?: boolean;
21
28
  ip?: string;
22
29
 
23
- /** If the SNI matches this domain, we use a different key/cert.
24
- * - Also requestCert may be specified (otherwise it defaults to true)
25
- */
30
+ /** If the SNI matches this domain, we use a different key/cert. */
26
31
  SNICerts?: {
27
- [domain: string]: https.ServerOptions;
32
+ [domain: string]: Watchable<https.ServerOptions>;
28
33
  };
29
34
  }
30
35
  );
@@ -32,25 +37,30 @@ export type SocketServerConfig = (
32
37
  export async function startSocketServer(
33
38
  config: SocketServerConfig
34
39
  ): Promise<string> {
35
- let isSecure = "cert" in config || "key" in config || "pfx" in config;
36
- if (!isSecure) {
37
- let { key, cert } = getCertKeyPair();
38
- config.key = key;
39
- config.cert = cert;
40
- }
41
-
42
40
 
43
41
  const webSocketServer = new ws.Server({
44
42
  noServer: true,
45
43
  });
46
44
 
47
- await loadTrustedUserCertificates();
45
+ async function setupHTTPSServer(watchOptions: Watchable<https.ServerOptions>) {
46
+ let httpsServerLast: https.Server | undefined;
47
+ let onHttpServer: (server: https.Server) => void;
48
+ let httpServerPromise = new Promise<https.Server>(r => onHttpServer = r);
49
+ let lastOptions!: https.ServerOptions;
50
+ await watchOptions(value => {
51
+ lastOptions = { ...value, ca: getTrustedCertificates() };
52
+ if (!httpsServerLast) {
53
+ httpsServerLast = https.createServer(lastOptions);
54
+ } else {
55
+ httpsServerLast.setSecureContext(lastOptions);
56
+ }
57
+ onHttpServer(httpsServerLast);
58
+ });
59
+ let httpsServer = await httpServerPromise;
48
60
 
49
- function setupHTTPSServer(options: https.ServerOptions) {
50
- let httpsServer = https.createServer(options);
51
- watchUserCertificates(() => {
52
- options.ca = tls.rootCertificates.concat(getTrustedUserCertificates());
53
- httpsServer.setSecureContext(options);
61
+ watchTrustedCertificates(() => {
62
+ lastOptions.ca = getTrustedCertificates();
63
+ httpsServer.setSecureContext(lastOptions);
54
64
  });
55
65
 
56
66
  httpsServer.on("connection", socket => {
@@ -102,15 +112,19 @@ export async function startSocketServer(
102
112
  // TODO: Only allow unauthorized for ip certificates, and then for domains use the domain as the nodeId,
103
113
  // so it is easy to read, and consistent.
104
114
  let options: https.ServerOptions = {
105
- rejectUnauthorized: SocketFunction.rejectUnauthorized,
106
- requestCert: true,
107
115
  ...config,
108
116
  };
117
+ if (!config.cert) {
118
+ throw new Error("No cert specified");
119
+ }
120
+ if (!config.key) {
121
+ throw new Error("No key specified");
122
+ }
109
123
 
110
- const mainHTTPSServer = setupHTTPSServer(options);
124
+ const mainHTTPSServer = await setupHTTPSServer(callback => callback(options));
111
125
  let sniServers = new Map<string, https.Server>();
112
126
  for (let [domain, obj] of Object.entries(config.SNICerts || {})) {
113
- sniServers.set(domain, setupHTTPSServer(obj));
127
+ sniServers.set(domain, await setupHTTPSServer(obj));
114
128
  }
115
129
 
116
130
  let httpServer = http.createServer({}, async function (req, res) {
@@ -137,10 +151,9 @@ export async function startSocketServer(
137
151
  if (buffer[0] !== 22) {
138
152
  server = httpServer;
139
153
  } else {
140
- debugbreak(1);
141
- debugger;
142
154
  let data = parseTLSHello(buffer);
143
155
  let sni = data.extensions.filter(x => x.type === SNIType).flatMap(x => parseSNIExtension(x.data))[0];
156
+ console.log(`Received TCP connection with SNI ${JSON.stringify(sni)}`);
144
157
  server = sniServers.get(sni) || mainHTTPSServer;
145
158
  }
146
159
 
@@ -169,18 +182,43 @@ export async function startSocketServer(
169
182
  host = "0.0.0.0";
170
183
  }
171
184
 
172
- console.log(`Trying to listening on ${host}:${config.port}`);
173
- realServer.listen(config.port, host);
185
+ let port = config.port;
186
+ if (config.useAvailablePortIfPortInUse && port) {
187
+ async function isPortInUse(port: number): Promise<boolean> {
188
+ return new Promise<boolean>((resolve, reject) => {
189
+ let server = net.createServer();
190
+ server.listen(port, host)
191
+ .on("listening", function () {
192
+ server.close();
193
+ resolve(false);
194
+ }).on("close", function () {
195
+ resolve(true);
196
+ }).on("error", function (e) {
197
+ resolve(true);
198
+ });
199
+ });
200
+ }
201
+ if (await isPortInUse(port)) {
202
+ port = 0;
203
+ }
204
+ }
205
+
206
+ console.log(`Trying to listening on ${host}:${port}`);
207
+ realServer.listen(port, host);
174
208
 
175
209
  await listenPromise;
176
210
 
177
- let port = (realServer.address() as net.AddressInfo).port;
211
+ port = (realServer.address() as net.AddressInfo).port;
212
+ let nodeId = getNodeId(getCommonName(config.cert), port);
213
+ console.log(`Started Listening on ${nodeId}`);
178
214
 
179
- console.log(`Started Listening on ${host}:${port}`);
215
+ return nodeId;
216
+ }
180
217
 
181
- let serverNodeId = getNodeIdFromCert({ raw: config.cert as Buffer | string }, port);
182
- if (!serverNodeId) {
183
- throw new Error(`Something is wrong with our cert, we don't have a nodeId?`);
184
- }
185
- return serverNodeId;
218
+ function getCommonName(cert: Buffer | string) {
219
+ let subject = new crypto.X509Certificate(cert).subject;
220
+ let subjectKVPs = new Map(subject.split(",").map(x => x.trim().split("=")).map(x => [x[0], x.slice(1).join("=")]));
221
+ let commonName = subjectKVPs.get("CN");
222
+ if (!commonName) throw new Error(`No common name in subject: ${subject}`);
223
+ return commonName;
186
224
  }
@@ -0,0 +1,46 @@
1
+ import ws from "ws";
2
+ import tls from "tls";
3
+ import { isNode } from "./misc";
4
+ import { SenderInterface } from "./CallFactory";
5
+ import { getTrustedCertificates } from "./certStore";
6
+ import { getNodeIdLocation } from "./nodeCache";
7
+ import debugbreak from "debugbreak";
8
+
9
+
10
+ export function getTLSSocket(webSocket: ws.WebSocket) {
11
+ return (webSocket as any)._socket as tls.TLSSocket;
12
+ }
13
+
14
+ /** NOTE: We create a factory, which embeds the key/cert information. Otherwise retries might use
15
+ * a different key/cert context.
16
+ */
17
+ export function createWebsocketFactory(): (nodeId: string) => SenderInterface {
18
+
19
+ if (!isNode()) {
20
+ return (nodeId: string) => {
21
+ let location = getNodeIdLocation(nodeId);
22
+ if (!location) throw new Error(`Cannot connect to ${nodeId}, no address known`);
23
+ let { address, port } = location;
24
+
25
+ console.log(`Connecting to ${address}:${port}`);
26
+ return new WebSocket(`wss://${address}:${port}`);
27
+ };
28
+ } else {
29
+ return (nodeId: string) => {
30
+ let location = getNodeIdLocation(nodeId);
31
+ if (!location) throw new Error(`Cannot connect to ${nodeId}, no address known`);
32
+ let { address, port } = location;
33
+
34
+ console.log(`Connecting to ${address}:${port}`);
35
+ let webSocket = new ws.WebSocket(`wss://${address}:${port}`, {
36
+ ca: tls.rootCertificates.concat(getTrustedCertificates()),
37
+ });
38
+ let result = Object.assign(webSocket, { socket: undefined as tls.TLSSocket | undefined });
39
+ webSocket.once("upgrade", e => {
40
+ result.socket = e.socket as tls.TLSSocket;
41
+ });
42
+ return result;
43
+ };
44
+ }
45
+ }
46
+
package/test/client.ts CHANGED
@@ -1,3 +1,5 @@
1
+ /// <reference path="../require/RequireController.ts" />
2
+
1
3
  // https://letx.ca:2542/?classGuid=RequireController-e2f811f3-14b8-4759-b0d6-73f14516cf1d&functionName=requireHTML&args=[%22./test/test%22]
2
4
 
3
5
  //import typescript from "typescript";
@@ -24,8 +26,6 @@ void main();
24
26
  async function main() {
25
27
  if (isNode()) return;
26
28
 
27
- SocketFunction.rejectUnauthorized = false;
28
-
29
29
  SocketFunction.expose(Test);
30
30
 
31
31
  console.log("cool");
package/test/shared.ts CHANGED
@@ -41,7 +41,7 @@ class TestBase {
41
41
  export const Test = SocketFunction.register(
42
42
  "80d9f328-72df-4baa-8be8-019c1003d4a2",
43
43
  new TestBase(),
44
- {
44
+ () => ({
45
45
  add: {
46
46
  // hooks: [
47
47
  // async (config) => {
@@ -49,17 +49,11 @@ export const Test = SocketFunction.register(
49
49
  // }
50
50
  // ]
51
51
  },
52
- callMe: {
53
- clientHooks: [
54
- async (config) => {
55
- config.call.reconnectTimeout = 2000;
56
- }
57
- ]
58
- },
52
+ callMe: {},
59
53
  callBack: {
60
54
 
61
55
  },
62
56
  //fncNotAsync: {},
63
57
  //notAFnc: {},
64
- }
58
+ })
65
59
  );
@@ -1,123 +0,0 @@
1
- import ws from "ws";
2
- import tls from "tls";
3
- import net from "net";
4
- import { getAppFolder } from "./storagePath";
5
- import fs from "fs";
6
- import child_process from "child_process";
7
- import { cacheWeak, lazy } from "./caching";
8
- import https from "https";
9
- import debugbreak from "debugbreak";
10
- import crypto from "crypto";
11
- import { isNode, sha256Hash } from "./misc";
12
- import { getArgs } from "./args";
13
- import { SenderInterface } from "./CallFactory";
14
- import { SocketFunction } from "../SocketFunction";
15
- import { getTrustedUserCertificates } from "./certStore";
16
- import { getClientNodeId, getNodeId, getNodeIdLocation } from "./nodeCache";
17
-
18
- export type CertInfo = { raw: Buffer | string; issuerCertificate: { raw: Buffer | string } };
19
-
20
- let certKeyPairOverride: { key: Buffer; cert: Buffer } | undefined;
21
- export function getCertKeyPair(): { key: Buffer; cert: Buffer } {
22
- if (certKeyPairOverride) return certKeyPairOverride;
23
- return getCertKeyPairBase();
24
- }
25
- const getCertKeyPairBase = lazy((): { key: Buffer; cert: Buffer } => {
26
- // https://nodejs.org/en/knowledge/HTTP/servers/how-to-create-a-HTTPS-server/
27
-
28
- let folder = getAppFolder();
29
- let identityPrefix = getArgs().identity || "";
30
- let keyPath = folder + identityPrefix + "key.pem";
31
- let certPath = folder + identityPrefix + "cert.pem";
32
- if (!fs.existsSync(keyPath) || !fs.existsSync(certPath)) {
33
- child_process.execSync(`openssl genrsa -out "${keyPath}"`);
34
- child_process.execSync(`openssl req -new -key "${keyPath}" -out csr.pem -subj "/CN=notused"`);
35
- child_process.execSync(`openssl x509 -req -days 9999 -in csr.pem -signkey "${keyPath}" -out "${certPath}"`);
36
- fs.rmSync("csr.pem");
37
- }
38
-
39
- let key = fs.readFileSync(keyPath);
40
- let cert = fs.readFileSync(certPath);
41
- return { key, cert };
42
- });
43
-
44
- export function overrideCertKeyPair<T>(certKey: { key: Buffer; cert: Buffer; }, code: () => T): T {
45
- if (!isNode()) {
46
- throw new Error(`Cannot override cert/key pair in browser`);
47
- }
48
- let prevOverride = certKeyPairOverride;
49
- certKeyPairOverride = certKey;
50
- try {
51
- return code();
52
- } finally {
53
- certKeyPairOverride = prevOverride;
54
- }
55
- }
56
-
57
- export function getTLSSocket(webSocket: ws.WebSocket) {
58
- return (webSocket as any)._socket as tls.TLSSocket;
59
- }
60
-
61
- export async function getOwnNodeId() {
62
- if (!isNode()) {
63
- throw new Error(`Clientside nodeIds are not exposed to the client`);
64
- }
65
-
66
- // This is BASICALLY just sha256Hash(getCertKeyPari().cert), however... I'm not 100% the format
67
- // is the same, we would have to verify it. It isn't that important, other nodes know our nodeId,
68
- // and clients don't really have a reason to use this anyway (they can't verify it, they can only
69
- // really verify with a location).
70
- throw new Error(`TODO: Implement getOwnNodeId`);
71
- }
72
-
73
- export function getNodeIdFromCert(certRaw: { raw: Buffer | string } | undefined, callbackPort: number | undefined) {
74
- if (!certRaw?.raw) return undefined;
75
- let cert = new crypto.X509Certificate(certRaw.raw);
76
- if (!callbackPort) {
77
- return getClientNodeId(cert.subject);
78
- }
79
- let subject = cert.subject;
80
- if (subject.startsWith("CN=")) {
81
- subject = subject.slice("CN=".length);
82
- }
83
- return getNodeId(subject, callbackPort);
84
- }
85
-
86
- /** NOTE: We create a factory, which embeds the key/cert information. Otherwise retries might use
87
- * a different key/cert context.
88
- */
89
- export function createWebsocketFactory(): (nodeId: string) => SenderInterface {
90
-
91
- if (!isNode()) {
92
- return (nodeId: string) => {
93
- let location = getNodeIdLocation(nodeId);
94
- if (!location) throw new Error(`Cannot connect to ${nodeId}, no address known`);
95
- let { address, port } = location;
96
-
97
- console.log(`Connecting to ${address}:${port}`);
98
- return new WebSocket(`wss://${address}:${port}`);
99
- };
100
- } else {
101
- let { key, cert } = getCertKeyPair();
102
- let rejectUnauthorized = SocketFunction.rejectUnauthorized;
103
- return (nodeId: string) => {
104
- let location = getNodeIdLocation(nodeId);
105
- if (!location) throw new Error(`Cannot connect to ${nodeId}, no address known`);
106
- let { address, port } = location;
107
-
108
- console.log(`Connecting to ${address}:${port}`);
109
- let webSocket = new ws.WebSocket(`wss://${address}:${port}`, {
110
- cert,
111
- key,
112
- rejectUnauthorized,
113
- ca: tls.rootCertificates.concat(getTrustedUserCertificates()),
114
- });
115
- let result = Object.assign(webSocket, { socket: undefined as tls.TLSSocket | undefined });
116
- webSocket.once("upgrade", e => {
117
- result.socket = e.socket as tls.TLSSocket;
118
- });
119
- return result;
120
- };
121
- }
122
- }
123
-