socket-function 0.12.3 → 0.12.4

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
@@ -211,7 +211,7 @@ export class SocketFunction {
211
211
  * to add additional imports to ensure the register call runs.
212
212
  */
213
213
  public static expose(socketRegistered: SocketRegistered) {
214
- console.log(blue(`Exposing Controller ${socketRegistered._classGuid}`));
214
+ console.log(`Exposing Controller ${blue(socketRegistered._classGuid)}`);
215
215
  exposeClass(socketRegistered);
216
216
  this.exposedClasses.add(socketRegistered._classGuid);
217
217
 
@@ -257,7 +257,12 @@ export class SocketFunction {
257
257
  return this.mountedNodeId;
258
258
  }
259
259
 
260
- /** Sets the default call when an http request is made, but no classGuid is set. */
260
+ /** Sets the default call when an http request is made, but no classGuid is set.
261
+ * NOTE: All other calls should be endpoint calls, even if those endpoints return a static file with an HTML content type.
262
+ * - However, to load new content, you should probably just use `require("./example.ts")`, which works on any files
263
+ * clientside that have also been required serverside (and whitelisted with module.allowclient = true,
264
+ * or with an `allowclient.flag` file in the directory or parent directory).
265
+ */
261
266
  public static setDefaultHTTPCall<
262
267
  Registered extends SocketRegistered,
263
268
  FunctionName extends keyof Registered["nodes"][""] & string,
@@ -7,7 +7,7 @@ import { cache, lazy } from "../src/caching";
7
7
  import * as fs from "fs";
8
8
  import debugbreak from "debugbreak";
9
9
  import { isNode } from "../src/misc";
10
- import { red } from "../src/formatting/logColors";
10
+ import { magenta, red } from "../src/formatting/logColors";
11
11
 
12
12
  /** Enables some hot reload functionality.
13
13
  * - Triggers a refresh clientside
@@ -35,8 +35,13 @@ export function watchFilesAndTriggerHotReloading(noAutomaticBrowserWatch = false
35
35
  declare global {
36
36
  namespace NodeJS {
37
37
  interface Module {
38
+ /** Causes us to hotreload the file. Applies both serverside and clientside.
39
+ * - If not set for any files clientside, we will refresh.
40
+ * - If not set for any files serverside, we will do nothing (and just leave old code running).
41
+ */
38
42
  hotreload?: boolean;
39
- noserverhotreload?: boolean;
43
+ /** Only hotreloads the file in the browser. */
44
+ hotreloadBrowser?: boolean;
40
45
  }
41
46
  }
42
47
  }
@@ -48,10 +53,20 @@ export function isHotReloading() {
48
53
  export function setExternalHotReloading(value: boolean) {
49
54
  isHotReloadingValue = value;
50
55
  }
56
+ let hotReloadCallbacks: ((modules: NodeJS.Module[]) => void)[] = [];
57
+ export function onHotReload(callback: (modules: NodeJS.Module[]) => void) {
58
+ hotReloadCallbacks.push(callback);
59
+ }
51
60
 
52
61
  const hotReloadModule = cache((module: NodeJS.Module) => {
53
62
  if (!module.updateContents) return;
54
- fs.watchFile(module.filename, { persistent: false, interval: 1000 }, (curr, prev) => {
63
+ let interval = 1000;
64
+ let fast = false;
65
+ if (module.hotreload || module.hotreloadBrowser) {
66
+ interval = 50;
67
+ fast = true;
68
+ }
69
+ fs.watchFile(module.filename, { persistent: false, interval }, (curr, prev) => {
55
70
  if (curr.mtime.getTime() === prev.mtime.getTime()) return;
56
71
  console.log(`Hot reloading due to change: ${module.filename}`);
57
72
  module.updateContents?.();
@@ -75,24 +90,32 @@ const hotReloadModule = cache((module: NodeJS.Module) => {
75
90
  }
76
91
  }
77
92
  }
78
- triggerClientSideReload();
93
+ triggerClientSideReload({
94
+ files: [module.filename],
95
+ changeTime: curr.mtimeMs,
96
+ fast,
97
+ });
79
98
  });
80
99
  });
81
100
  let reloadTriggering = false;
82
101
  let clientWatcherNodes = new Set<string>();
83
- function triggerClientSideReload() {
102
+ function triggerClientSideReload(config: {
103
+ files: string[];
104
+ changeTime: number;
105
+ fast?: boolean;
106
+ }) {
84
107
  if (reloadTriggering) return;
85
108
  reloadTriggering = true;
86
109
  setTimeout(async () => {
87
110
  reloadTriggering = false;
88
111
  for (let clientNodeId of clientWatcherNodes) {
89
112
  console.log(`Notifying client of hot reload: ${clientNodeId}`);
90
- HotReloadController.nodes[clientNodeId].fileUpdated().catch(() => {
113
+ HotReloadController.nodes[clientNodeId].fileUpdated(config.files, config.changeTime).catch(() => {
91
114
  console.log(`Removing erroring client: ${clientNodeId}`);
92
115
  clientWatcherNodes.delete(clientNodeId);
93
116
  });
94
117
  }
95
- }, 300);
118
+ }, config.fast ? 10 : 300);
96
119
  }
97
120
 
98
121
  class HotReloadControllerBase {
@@ -102,8 +125,36 @@ class HotReloadControllerBase {
102
125
  let callerId = SocketFunction.getCaller().nodeId;
103
126
  clientWatcherNodes.add(callerId);
104
127
  }
105
- async fileUpdated() {
106
- document.location.reload();
128
+ async fileUpdated(files: string[], changeTime: number) {
129
+ console.groupCollapsed(magenta(`Trigger hotreload for files (${Date.now() - changeTime}ms after file change)`));
130
+ for (let file of files) {
131
+ console.log(file);
132
+ }
133
+ console.groupEnd();
134
+ let modules: NodeJS.Module[] = [];
135
+ for (let file of files) {
136
+ let module = require.cache[file];
137
+ if (!module) {
138
+ console.log(`Module not found: ${file}, reloading page to ensure new version is loaded`);
139
+ document.location.reload();
140
+ return;
141
+ }
142
+ if (!module.hotreload && !module.hotreloadBrowser) {
143
+ console.log(`Module not hotreloadable: ${file}, reloading page to ensure new version is loaded`);
144
+ document.location.reload();
145
+ return;
146
+ }
147
+ modules.push(module);
148
+ }
149
+ for (let module of modules) {
150
+ module.loaded = false;
151
+ }
152
+ await Promise.all(modules.map(module => module.load(module.filename)));
153
+
154
+ for (let callback of hotReloadCallbacks) {
155
+ callback(modules);
156
+ }
157
+ console.log(magenta(`Hot reload complete (${Date.now() - changeTime}ms after file change)`));
107
158
  }
108
159
  }
109
160
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.12.3",
3
+ "version": "0.12.4",
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",
@@ -98,13 +98,17 @@ function addMapGetModules(remap: typeof mapGetModules[number]["remap"]) {
98
98
  class RequireControllerBase {
99
99
  public rootResolvePath = "";
100
100
 
101
- public async requireHTML(bootRequirePath?: string) {
101
+ public async requireHTML(config?: {
102
+ requireCalls?: string[];
103
+ }) {
104
+ let { requireCalls } = config || {};
102
105
  let result = resolvedHTMLFile;
103
106
  if (beforeEntryText.length > 0) {
104
107
  result = result.replace(BEFORE_ENTRY_TEMPLATE, beforeEntryText.join("\n"));
105
108
  }
106
- if (bootRequirePath) {
107
- result = result.replace(ENTRY_TEMPLATE, `<script>require(${JSON.stringify(bootRequirePath)});</script>`);
109
+ if (requireCalls) {
110
+ result = result.replace(ENTRY_TEMPLATE, `<script>\n${requireCalls.map(x => `require(${JSON.stringify(x)});`).join(" \n")
111
+ }</script>`);
108
112
  }
109
113
  return setHTTPResultHeaders(Buffer.from(result), { "Content-Type": "text/html" });
110
114
  }
@@ -245,22 +245,27 @@
245
245
  return builtInModuleExports[request];
246
246
  }
247
247
 
248
- if (!(request in serializedModule.requests)) {
249
- if (!asyncIsFine) {
250
- 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.`,
251
- "color: red", "color: unset",
252
- "color: red", "color: unset",
253
- );
248
+ let resolvedPath;
249
+ if (request in moduleCache) {
250
+ resolvedPath = request;
251
+ } else {
252
+ if (!(request in serializedModule.requests)) {
253
+ if (!asyncIsFine) {
254
+ 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.`,
255
+ "color: red", "color: unset",
256
+ "color: red", "color: unset",
257
+ );
258
+ }
259
+ return rootRequire(request);
254
260
  }
255
- return rootRequire(request);
256
- }
257
261
 
258
- // Built in modules that we haven't been implemented
259
- if (serializedModule.requests[request] === "") {
260
- return {};
261
- }
262
+ // Built in modules that we haven't been implemented
263
+ if (serializedModule.requests[request] === "") {
264
+ return {};
265
+ }
262
266
 
263
- let resolvedPath = serializedModule.requests[request];
267
+ resolvedPath = serializedModule.requests[request];
268
+ }
264
269
  if (resolvedPath !== "NOTALLOWEDCLIENTSIDE" && !serializedModules[resolvedPath]) {
265
270
  if (!asyncIsFine) {
266
271
  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.`,
@@ -381,11 +386,10 @@
381
386
  delete alreadyHave.seqNums[serializedModule.seqNum];
382
387
  }
383
388
  // NOTE: There is almost never recovery from module downloading errors, so just don't catch them
384
- void Promise.resolve().then(() => rootRequire(resolvedId, true)).then(() => {
389
+ return Promise.resolve().then(() => rootRequire(resolvedId, true)).then(async () => {
385
390
  module.loaded = true;
386
- load();
391
+ await load();
387
392
  });
388
- return;
389
393
  }
390
394
 
391
395
  module.requires = serializedModule.requests;
@@ -16,6 +16,7 @@ import { delay, runInfinitePoll } from "./batching";
16
16
  import { magenta } from "./formatting/logColors";
17
17
  import { yellow } from "./formatting/logColors";
18
18
  import { green } from "./formatting/logColors";
19
+ import { formatTime } from "./formatting/format";
19
20
 
20
21
  export type SocketServerConfig = (
21
22
  https.ServerOptions & {
@@ -210,21 +211,21 @@ export async function startSocketServer(
210
211
  }
211
212
 
212
213
  let port = config.port;
214
+ async function isPortInUse(port: number): Promise<boolean> {
215
+ return new Promise<boolean>((resolve, reject) => {
216
+ let server = net.createServer();
217
+ server.listen(port, host)
218
+ .on("listening", function () {
219
+ server.close();
220
+ resolve(false);
221
+ }).on("close", function () {
222
+ resolve(true);
223
+ }).on("error", function (e) {
224
+ resolve(true);
225
+ });
226
+ });
227
+ }
213
228
  if (config.useAvailablePortIfPortInUse && port) {
214
- async function isPortInUse(port: number): Promise<boolean> {
215
- return new Promise<boolean>((resolve, reject) => {
216
- let server = net.createServer();
217
- server.listen(port, host)
218
- .on("listening", function () {
219
- server.close();
220
- resolve(false);
221
- }).on("close", function () {
222
- resolve(true);
223
- }).on("error", function (e) {
224
- resolve(true);
225
- });
226
- });
227
- }
228
229
  if (await isPortInUse(port)) {
229
230
  port = 0;
230
231
  }
@@ -239,7 +240,7 @@ export async function startSocketServer(
239
240
 
240
241
  port = (realServer.address() as net.AddressInfo).port;
241
242
  let nodeId = getNodeId(getCommonName(config.cert), port);
242
- console.log(green(`Started Listening on ${nodeId}`));
243
+ console.log(green(`Started Listening on ${nodeId} after ${formatTime(process.uptime() * 1000)}`));
243
244
 
244
245
  return nodeId;
245
246
  }