socket-function 0.8.40 → 0.9.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.
package/SocketFunction.ts CHANGED
@@ -249,6 +249,10 @@ export class SocketFunction {
249
249
  return getNodeId(location.address, location.port);
250
250
  }
251
251
 
252
+ public static locationNode() {
253
+ return SocketFunction.connect({ address: location.hostname, port: +location.port });
254
+ }
255
+
252
256
  public static addGlobalHook(hook: SocketFunctionHook<SocketExposedInterface>) {
253
257
  registerGlobalHook(hook as SocketFunctionHook);
254
258
  }
@@ -5,12 +5,25 @@ module.allowclient = true;
5
5
  import { SocketFunction } from "../SocketFunction";
6
6
  import { cache, lazy } from "../src/caching";
7
7
  import * as fs from "fs";
8
+ import debugbreak from "debugbreak";
9
+ import { isNode } from "../src/misc";
10
+ import { red } from "../src/formatting/logColors";
8
11
 
9
- /** Hot reloads server and client files, just trigger a refresh clientside,
10
- * while triggering per file re-evaluation and export updates serverside.
11
- * - Requires HotReloadController to be exposed both serverside and clientside.
12
+ /** Enables some hot reload functionality.
13
+ * - Triggers a refresh clientside
14
+ * - Triggers a reload server, for modules marked with `module.hotreload`
12
15
  */
13
- export function watchFilesAndTriggerHotReloading() {
16
+ export function watchFilesAndTriggerHotReloading(noAutomaticBrowserWatch = false) {
17
+
18
+ SocketFunction.expose(HotReloadController);
19
+ if (!isNode()) {
20
+ if (!noAutomaticBrowserWatch) {
21
+ HotReloadController.nodes[SocketFunction.locationNode()]
22
+ .watchFiles()
23
+ .catch(e => console.error("watchFiles error", e))
24
+ ;
25
+ }
26
+ }
14
27
  setInterval(() => {
15
28
  for (let module of Object.values(require.cache)) {
16
29
  if (!module) continue;
@@ -19,6 +32,14 @@ export function watchFilesAndTriggerHotReloading() {
19
32
  }, 5000);
20
33
  }
21
34
 
35
+ declare global {
36
+ namespace NodeJS {
37
+ interface Module {
38
+ hotreload?: boolean;
39
+ noserverhotreload?: boolean;
40
+ }
41
+ }
42
+ }
22
43
 
23
44
  const hotReloadModule = cache((module: NodeJS.Module) => {
24
45
  if (!module.updateContents) return;
@@ -26,6 +47,23 @@ const hotReloadModule = cache((module: NodeJS.Module) => {
26
47
  if (curr.mtime.getTime() === prev.mtime.getTime()) return;
27
48
  console.log(`Hot reloading due to change: ${module.filename}`);
28
49
  module.updateContents?.();
50
+ if (isNode()) {
51
+ if (
52
+ module.hotreload
53
+ // A fairly big hack (as this could just be in a string, or something similar), but... it also VERY useful
54
+ || module.moduleContents?.includes("\nmodule.hotreload = true;" + "\n")
55
+ || module.moduleContents?.includes("\r\nmodule.hotreload = true;" + "\r\n")
56
+ ) {
57
+ console.log(`Reloading ${module.id}`);
58
+ try {
59
+ module.loaded = false;
60
+ module.load(module.id);
61
+ } catch (e) {
62
+ console.error(red(`Error hot reloading ${module.id}`));
63
+ console.error(e);
64
+ }
65
+ }
66
+ }
29
67
  triggerClientSideReload();
30
68
  });
31
69
  });
@@ -50,10 +88,7 @@ class HotReloadControllerBase {
50
88
  // TODO: Also hot reload when we reconnect to the server, as it is likely setup will need to
51
89
  // be rerun in that case as well (for example, we need to call watchFiles again!)
52
90
  async watchFiles() {
53
- let callerId = HotReloadController.context.caller?.nodeId;
54
- if (!callerId) {
55
- throw new Error("No nodeId?");
56
- }
91
+ let callerId = SocketFunction.getCaller().nodeId;
57
92
  clientWatcherNodes.add(callerId);
58
93
  }
59
94
  async fileUpdated() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.8.40",
3
+ "version": "0.9.0",
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,8 +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
- "rdtsc-now": "^0.3.0",
13
- "typenode": "^4.9.4-f",
12
+ "rdtsc-now": "^0.3.1",
13
+ "typenode": "^4.9.4-g",
14
14
  "ws": "^8.8.0"
15
15
  },
16
16
  "scripts": {
@@ -1,4 +1,6 @@
1
1
  (function () {
2
+ //# sourceURL=require.js
3
+
2
4
  // Globals
3
5
  Object.assign(window, {
4
6
  process: {
@@ -374,9 +374,15 @@ export async function createCallFactory(
374
374
  }
375
375
  throw new Error(`Unhandled data type ${typeof message}`);
376
376
  } catch (e: any) {
377
- debugbreak(2);
378
- debugger;
379
- console.error(e.stack);
377
+ // NOTE: I'm looking for all types of errors here (specifically, .send errors), in case
378
+ // there are errors I should be handling.
379
+ if (e.stack.startsWith("Error: Cannot send data to") && e.stack.includes("as the connection has closed")) {
380
+ // This is fine, just ignore it
381
+ } else {
382
+ debugbreak(2);
383
+ debugger;
384
+ console.error(e.stack);
385
+ }
380
386
  }
381
387
  }
382
388
 
package/src/batching.ts CHANGED
@@ -113,15 +113,18 @@ export async function runInfinitePollCallAtStart(
113
113
  delayTime: number,
114
114
  fnc: () => Promise<void> | void
115
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}`);
116
+ try {
117
+ return await fnc();
118
+ } finally {
119
+ void (async () => {
120
+ while (true) {
121
+ await delay(delayTime);
122
+ try {
123
+ await fnc();
124
+ } catch (e: any) {
125
+ console.error(`Error in infinite poll ${fnc.name} (continuing poll loop)\n${e.stack}`);
126
+ }
123
127
  }
124
- }
125
- })();
126
- return await fnc();
128
+ })();
129
+ }
127
130
  }
@@ -158,7 +158,7 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
158
158
 
159
159
 
160
160
  // NOTE: Our ETag caching is only to reduce data sent on the wire, we evaluate the calls
161
- // every time (so it is strictly a wire cache, not a computation cache)
161
+ // every time (so it is strictly a wire cache for HTTP, not a computation cache)
162
162
  if (SocketFunction.httpETagCache) {
163
163
  response.setHeader("cache-control", "private, max-age=0, must-revalidate");
164
164
  let hash = sha256Hash(resultBuffer);
package/src/certStore.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as tls from "tls";
2
- import { sha256Hash } from "./misc";
2
+ import { isNode, sha256Hash } from "./misc";
3
3
 
4
4
  let trustedCerts = new Set<string>();
5
5
  let watchCallbacks = new Set<(certs: string[]) => void>();
@@ -15,8 +15,12 @@ export function trustCertificate(cert: string | Buffer) {
15
15
  }
16
16
  }
17
17
  export function getTrustedCertificates(): string[] {
18
- //console.log(`trustedCerts = ${Array.from(trustedCerts).map(x => sha256Hash(x).slice(0, 10))}`);
19
- return tls.rootCertificates.concat(Array.from(trustedCerts));
18
+ let certs: string[] = [];
19
+ if (isNode()) {
20
+ certs.push(...tls.rootCertificates);
21
+ }
22
+ certs.push(...Array.from(trustedCerts));
23
+ return certs;
20
24
  }
21
25
 
22
26
  export function watchTrustedCertificates(callback: (certs: string[]) => void) {
package/src/misc.ts CHANGED
@@ -148,4 +148,25 @@ if (isNode()) {
148
148
  process.on("unhandledRejection", async (reason: any, promise) => {
149
149
  console.error(`Uncaught promise rejection: ${String(reason.stack || reason)}`);
150
150
  });
151
+ }
152
+
153
+ export function keyBy<T, K>(arr: T[], getKey: (value: T) => K): Map<K, T> {
154
+ let map = new Map<K, T>();
155
+ for (let item of arr) {
156
+ map.set(getKey(item), item);
157
+ }
158
+ return map;
159
+ }
160
+ export function keyByArray<T, K>(arr: T[], getKey: (value: T) => K): Map<K, T[]> {
161
+ let map = new Map<K, T[]>();
162
+ for (let item of arr) {
163
+ let key = getKey(item);
164
+ let arr = map.get(key);
165
+ if (!arr) {
166
+ arr = [];
167
+ map.set(key, arr);
168
+ }
169
+ arr.push(item);
170
+ }
171
+ return map;
151
172
  }
@@ -13,6 +13,9 @@ import { getNodeId } from "./nodeCache";
13
13
  import crypto from "crypto";
14
14
  import { Watchable } from "./misc";
15
15
  import { delay, runInfinitePoll } from "./batching";
16
+ import { magenta } from "./formatting/logColors";
17
+ import { yellow } from "./formatting/logColors";
18
+ import { green } from "./formatting/logColors";
16
19
 
17
20
  export type SocketServerConfig = (
18
21
  https.ServerOptions & {
@@ -28,6 +31,11 @@ export type SocketServerConfig = (
28
31
  public?: boolean;
29
32
  ip?: string;
30
33
 
34
+ // NOTE: Any same origin accesses are allowed (header.origin === header.host)
35
+ // For example, to allow "letx.ca" to access the server (when the hosted domain
36
+ // may be, "querysub.com", for example), use ["letx.ca"]
37
+ allowHostnames?: string[];
38
+
31
39
  /** If the SNI matches this domain, we use a different key/cert. */
32
40
  SNICerts?: {
33
41
  [domain: string]: Watchable<https.ServerOptions>;
@@ -59,6 +67,11 @@ export async function startSocketServer(
59
67
  });
60
68
  let httpsServer = await httpServerPromise;
61
69
 
70
+ let allowedHostnames = new Set<string>();
71
+ for (let hostname of config.allowHostnames || []) {
72
+ allowedHostnames.add(hostname);
73
+ }
74
+
62
75
  watchTrustedCertificates(() => {
63
76
  lastOptions.ca = getTrustedCertificates();
64
77
  httpsServer.setSecureContext(lastOptions);
@@ -100,8 +113,8 @@ export async function startSocketServer(
100
113
  try {
101
114
  let host = new URL("ws://" + request.headers["host"]).hostname;
102
115
  let origin = new URL(originHeader).hostname;
103
- if (host !== origin) {
104
- throw new Error(`Invalid cross thread request, ${JSON.stringify(host)} !== ${JSON.stringify(origin)}`);
116
+ if (host !== origin && !allowedHostnames.has(origin)) {
117
+ throw new Error(`Invalid cross domain request, ${JSON.stringify(host)} !== ${JSON.stringify(origin)} (also not config.allowedHostnames ${JSON.stringify(config.allowHostnames)})`);
105
118
  }
106
119
  } catch (e) {
107
120
  console.error(e);
@@ -214,7 +227,7 @@ export async function startSocketServer(
214
227
  }
215
228
 
216
229
  if (!SocketFunction.silent) {
217
- console.log(`Trying to listening on ${host}:${port}`);
230
+ console.log(yellow(`Trying to listening on ${host}:${port}`));
218
231
  }
219
232
  realServer.listen(port, host);
220
233
 
@@ -223,7 +236,7 @@ export async function startSocketServer(
223
236
  port = (realServer.address() as net.AddressInfo).port;
224
237
  let nodeId = getNodeId(getCommonName(config.cert), port);
225
238
  if (!SocketFunction.silent) {
226
- console.log(`Started Listening on ${nodeId}`);
239
+ console.log(green(`Started Listening on ${nodeId}`));
227
240
  }
228
241
 
229
242
  return nodeId;
package/test/shared.ts CHANGED
@@ -8,19 +8,13 @@ class TestBase {
8
8
  memberVariable = 5;
9
9
 
10
10
  async add(lhs: number, rhs: number) {
11
- let caller = Test.context.caller?.nodeId;
12
- if (!caller) {
13
- throw new Error("No caller?");
14
- }
11
+ let caller = SocketFunction.getCaller().nodeId;
15
12
  console.log(`Caller is ${caller}`);
16
13
  return lhs + rhs;
17
14
  }
18
15
 
19
16
  async callMe() {
20
- let caller = Test.context.caller?.nodeId;
21
- if (!caller) {
22
- throw new Error("No caller?");
23
- }
17
+ let caller = SocketFunction.getCaller().nodeId;
24
18
  console.log(`Caller is ${caller}`);
25
19
  void (async () => {
26
20
  let seqNum = 1;