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 +4 -0
- package/hot/HotReloadController.ts +43 -8
- package/package.json +3 -3
- package/require/require.js +2 -0
- package/src/CallFactory.ts +9 -3
- package/src/batching.ts +13 -10
- package/src/callHTTPHandler.ts +1 -1
- package/src/certStore.ts +7 -3
- package/src/misc.ts +21 -0
- package/src/webSocketServer.ts +17 -4
- package/test/shared.ts +2 -8
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
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
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 =
|
|
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.
|
|
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.
|
|
13
|
-
"typenode": "^4.9.4-
|
|
12
|
+
"rdtsc-now": "^0.3.1",
|
|
13
|
+
"typenode": "^4.9.4-g",
|
|
14
14
|
"ws": "^8.8.0"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
package/require/require.js
CHANGED
package/src/CallFactory.ts
CHANGED
|
@@ -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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
}
|
package/src/callHTTPHandler.ts
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
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
|
}
|
package/src/webSocketServer.ts
CHANGED
|
@@ -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
|
|
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 =
|
|
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 =
|
|
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;
|