socket-function 0.5.0 → 0.6.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.
@@ -0,0 +1,140 @@
1
+ import https from "https";
2
+ import http from "http";
3
+ import net from "net";
4
+ import tls from "tls";
5
+ import * as ws from "ws";
6
+ import { performLocalCall } from "./callManager";
7
+ import { CallerContext, CallType, NetworkLocation } from "../SocketFunctionTypes";
8
+ import { CallFactory, callFactoryFromWS } from "./CallFactory";
9
+ import { registerNodeClient } from "./nodeCache";
10
+ import { getCertKeyPair, getNodeId, getNodeIdRaw } from "./nodeAuthentication";
11
+ import debugbreak from "debugbreak";
12
+ import { cache } from "./caching";
13
+ import { getNodeIdFromRequest, httpCallHandler } from "./callHTTPHandler";
14
+
15
+ // TODO: Support conditional peer certificate requests, as it the certificate prompt
16
+ // seems suspicious in the browser (the user can just click cancel though).
17
+
18
+ export type SocketServerConfig = (
19
+ {
20
+ port: number;
21
+ // public sets ip to "0.0.0.0", otherwise it defaults to "127.0.0.1", which
22
+ // causes the server to only accept local connections.
23
+ public?: boolean;
24
+ ip?: string;
25
+ } & (
26
+ https.ServerOptions
27
+ )
28
+ );
29
+
30
+ export async function startSocketServer(
31
+ config: SocketServerConfig
32
+ ) {
33
+ let isSecure = "cert" in config || "key" in config || "pfx" in config;
34
+ if (!isSecure) {
35
+ let { key, cert } = getCertKeyPair();
36
+ config.key = key;
37
+ config.cert = cert;
38
+ }
39
+
40
+ // TODO: Only allow unauthorized for ip certificates, and then for domains use the domain as the nodeId,
41
+ // so it is easy to read, and consistent.
42
+ let httpsServer = https.createServer({
43
+ ...config,
44
+ rejectUnauthorized: false,
45
+ requestCert: true,
46
+ });
47
+
48
+
49
+ httpsServer.on("request", httpCallHandler);
50
+
51
+ const webSocketServer = new ws.Server({
52
+ noServer: true,
53
+ });
54
+ httpsServer.on("upgrade", (request, socket, upgradeHead) => {
55
+ webSocketServer.handleUpgrade(request, socket, upgradeHead, async (ws) => {
56
+ // NOTE: For the browser, the request will likely have a nodeId, from making an HTTP request.
57
+ // We would prefer peer certificates, so this isn't the default (in getNodeId), but it will
58
+ // likely be used most of the time.
59
+ let requestNodeId = getNodeIdFromRequest(request);
60
+ Object.assign(ws, { nodeId: requestNodeId });
61
+
62
+ let clientCallFactory = await callFactoryFromWS(ws);
63
+ registerNodeClient(clientCallFactory);
64
+ });
65
+ });
66
+
67
+ let httpServer = http.createServer({}, async function (req, res) {
68
+ let url = new URL("http://" + req.headers.host + req.url);
69
+ url.protocol = "https:";
70
+ //url.hostname = opts.hostname;
71
+ url.hostname = req.headers.host || "";
72
+ res.writeHead(301, { Location: url + "" });
73
+ res.end();
74
+ });
75
+
76
+ httpServer.listen(0, "127.0.0.1");
77
+ httpsServer.listen(0, "127.0.0.1");
78
+
79
+ // TODO: We should really add error handling here, but... we should always be able to listen
80
+ // on ANY port on localhost, as why couldn't we?
81
+ let httpServerReady = new Promise(resolve => httpServer.once("listening", resolve));
82
+ let httpsServerReady = new Promise(resolve => httpsServer.once("listening", resolve));
83
+ await httpServerReady;
84
+ await httpsServerReady;
85
+
86
+ let httpAddress = httpServer.address() as net.AddressInfo;
87
+ let httpsAddress = httpsServer.address() as net.AddressInfo;
88
+
89
+
90
+ let realServer = net.createServer(socket => {
91
+ // NOTE: ONCE is used, so we only look at the first buffer, and then after that
92
+ // we pipe. This should be very efficient, as pipe has insane throughput
93
+ // (100s of MB/s, easily, even on a terrible machine).
94
+ socket.once("data", buffer => {
95
+ // All HTTPS requests start with 22, and no HTTP requests start with 22,
96
+ // so we just need to read the first byte.
97
+ let byte = buffer[0];
98
+ let isHTTPS = byte === 22;
99
+ let address = httpAddress;
100
+ if (isHTTPS) {
101
+ address = httpsAddress;
102
+ }
103
+ let baseSocket = net.connect(address.port);
104
+
105
+ baseSocket.write(buffer);
106
+ socket.pipe(baseSocket);
107
+ baseSocket.pipe(socket);
108
+
109
+ baseSocket.on("error", (e) => {
110
+ console.error(`Base socket error, ${e.stack}`);
111
+ });
112
+ });
113
+ socket.on("error", (e) => {
114
+ console.error(`Exposed socket error, ${e.stack}`);
115
+ });
116
+ });
117
+
118
+
119
+ let listenPromise = new Promise<void>((resolve, error) => {
120
+ realServer.on("listening", () => {
121
+ resolve();
122
+ });
123
+ realServer.on("error", e => {
124
+ error(e);
125
+ });
126
+ });
127
+
128
+
129
+ let host = config.ip ?? "127.0.0.1";
130
+ if (config.public) {
131
+ host = "0.0.0.0";
132
+ }
133
+
134
+ console.log(`Trying to listening on ${host}:${config.port}`);
135
+ realServer.listen(config.port, host);
136
+
137
+ await listenPromise;
138
+
139
+ console.log(`Started Listening on ${host}:${config.port}`);
140
+ }
@@ -2,9 +2,9 @@ import os from "os";
2
2
  import fs from "fs";
3
3
  import { lazy } from "./caching";
4
4
 
5
- export const getAppFolder = lazy(() => {
5
+ export const getAppFolder = lazy(() => {
6
6
  const path = os.homedir() + "/socket-function/";
7
- if(!fs.existsSync(path)) {
7
+ if (!fs.existsSync(path)) {
8
8
  fs.mkdirSync(path);
9
9
  }
10
10
  return path;
File without changes
File without changes
@@ -0,0 +1,3 @@
1
+ body {
2
+ background: orange;
3
+ }
package/test/client.ts ADDED
@@ -0,0 +1,42 @@
1
+ // https://letx.ca:2542/?classGuid=RequireController-e2f811f3-14b8-4759-b0d6-73f14516cf1d&functionName=requireHTML&args=[%22./test/test%22]
2
+
3
+ //import typescript from "typescript";
4
+
5
+ import debugbreak from "debugbreak";
6
+ import { setFlag } from "../require/compileFlags";
7
+ import { SocketFunction } from "../SocketFunction";
8
+ import { Test } from "./shared";
9
+
10
+ import "./client.css";
11
+ import { isNode } from "../src/misc";
12
+
13
+ module.allowclient = true;
14
+
15
+ //console.log(typeof typescript);
16
+
17
+ //setFlag(require, "typescript", "allowclient", true);
18
+
19
+
20
+ void main();
21
+
22
+ async function main() {
23
+ if (isNode()) return;
24
+
25
+ SocketFunction.expose(Test);
26
+
27
+ console.log("cool");
28
+
29
+ const port = 2542;
30
+
31
+ let serverId = await SocketFunction.connect({ port, address: "letx.ca" });
32
+ let test = await Test.nodes[serverId].add(1, 2);
33
+ console.log(`${test}=${1 + 2}`);
34
+
35
+ // while (true) {
36
+ // let test = await TestClass.nodes[serverId].add(1, 2);
37
+ // console.log(`${test}=${1 + 2}`);
38
+ // await new Promise(resolve => setTimeout(resolve, 1000));
39
+ // }
40
+
41
+ await Test.nodes[serverId].callMe();
42
+ }
package/test/server.ts ADDED
@@ -0,0 +1,34 @@
1
+ // import debugbreak from "debugbreak";
2
+ // debugbreak(1);
3
+ // debugger;
4
+
5
+ import { RequireControllerFactory } from "../require/RequireController";
6
+ import { SocketFunction } from "../SocketFunction";
7
+ import { getArgs } from "../src/args";
8
+ import { Test } from "./shared";
9
+ import path from "path";
10
+ import { compileTransform, compileTransformBefore } from "typenode";
11
+
12
+ // Must add CSS shim before we import any clientside files
13
+ // NOTE: The css shim only need to run serverside, as clientside doesn't run compilation,
14
+ // and instead just copies serverside module contents.
15
+ import "../require/CSSShim";
16
+
17
+ // Import clientside files, so they can be whitelisted
18
+ import "./client";
19
+
20
+
21
+ void main();
22
+
23
+ async function main() {
24
+ SocketFunction.expose(Test);
25
+
26
+ let RequireController = RequireControllerFactory(path.dirname(__dirname));
27
+ SocketFunction.expose(RequireController);
28
+
29
+ SocketFunction.setDefaultHTTPCall(RequireController, "requireHTML", "./test/client");
30
+
31
+ const port = 2542;
32
+
33
+ await SocketFunction.mount({ port });
34
+ }
package/test/shared.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { getArgs } from "../src/args";
2
+ import { SocketFunction } from "../SocketFunction";
3
+
4
+ import "typenode";
5
+ module.moduleContents;
6
+
7
+ class TestBase {
8
+ memberVariable = 5;
9
+
10
+ async add(lhs: number, rhs: number) {
11
+ let caller = Test.context.caller?.nodeId;
12
+ if (!caller) {
13
+ throw new Error("No caller?");
14
+ }
15
+ console.log(`Caller is ${caller}`);
16
+ return lhs + rhs;
17
+ }
18
+
19
+ async callMe() {
20
+ let caller = Test.context.caller?.nodeId;
21
+ if (!caller) {
22
+ throw new Error("No caller?");
23
+ }
24
+ console.log(`Caller is ${caller}`);
25
+ void (async () => {
26
+ let seqNum = 1;
27
+ while (true) {
28
+ console.log(`Calling client at ${seqNum}`);
29
+ await Test.nodes[caller].callBack();
30
+ await new Promise(resolve => setTimeout(resolve, 1000));
31
+ seqNum++;
32
+ }
33
+ })();
34
+ }
35
+
36
+ async callBack() {
37
+ console.log(`Got callback at ${Date.now()}`);
38
+ }
39
+ }
40
+
41
+ export const Test = SocketFunction.register(
42
+ "80d9f328-72df-4baa-8be8-019c1003d4a2",
43
+ new TestBase(),
44
+ {
45
+ add: {
46
+ // hooks: [
47
+ // async (config) => {
48
+
49
+ // }
50
+ // ]
51
+ },
52
+ callMe: {
53
+ clientHooks: [
54
+ async (config) => {
55
+ config.call.reconnectTimeout = 2000;
56
+ }
57
+ ]
58
+ },
59
+ callBack: {
60
+
61
+ },
62
+ //fncNotAsync: {},
63
+ //notAFnc: {},
64
+ }
65
+ );
package/socketServer.ts DELETED
@@ -1,74 +0,0 @@
1
- import https from "https";
2
- import http from "http";
3
- import net from "net";
4
- import * as ws from "ws";
5
- import { performLocalCall } from "./callManager";
6
- import { CallerContext, CallType, NetworkLocation } from "./SocketFunctionTypes";
7
- import { callFactoryFromWS } from "./CallInstance";
8
- import { registerNodeClient } from "./nodeCache";
9
- import { getCertKeyPair } from "./nodeAuthentication";
10
-
11
- export type SocketServerConfig = {
12
- port: number;
13
- // public sets ip to "0.0.0.0", otherwise it defaults to "127.0.0.1", which
14
- // causes the server to only accept local connections.
15
- public?: boolean;
16
- ip?: string;
17
- } & (
18
- https.ServerOptions
19
- );
20
-
21
- export async function startSocketServer(
22
- config: SocketServerConfig
23
- ) {
24
- let isSecure = "cert" in config || "key" in config || "pfx" in config;
25
- if (!isSecure) {
26
- let { key, cert } = getCertKeyPair();
27
- config.key = key;
28
- config.cert = cert;
29
- }
30
-
31
- // TODO: Only allow unauthorized for ip certificates, and then for domains use the domain as the nodeId,
32
- // so it is easy to read, and consistent.
33
- let server = https.createServer({
34
- ...config,
35
- rejectUnauthorized: false,
36
- requestCert: true
37
- });
38
- let listenPromise = new Promise<void>((resolve, error) => {
39
- server.on("listening", () => {
40
- resolve();
41
- });
42
- server.on("error", e => {
43
- error(e);
44
- });
45
- });
46
-
47
-
48
- let host = config.ip ?? "127.0.0.1";
49
- if (config.public) {
50
- host = "0.0.0.0";
51
- }
52
-
53
- server.on("request", (request, response) => {
54
- // TODO: Handle HTTP requests
55
- // - HTTP CAN have a nodeId, simply through setting cookies
56
- // - Cookies could always be set via a request before we open
57
- // the websocket connection?
58
- });
59
-
60
- const webSocketServer = new ws.Server({
61
- noServer: true,
62
- });
63
- server.on("upgrade", (request, socket, upgradeHead) => {
64
- webSocketServer.handleUpgrade(request, socket, upgradeHead, async (ws) => {
65
- let clientCallFactory = await callFactoryFromWS(ws);
66
- registerNodeClient(clientCallFactory);
67
- });
68
- });
69
-
70
- console.log(`Listening on ${host}:${config.port}`);
71
- server.listen(config.port, host);
72
-
73
- return await listenPromise;
74
- }
package/test.ts DELETED
@@ -1,87 +0,0 @@
1
- import { getArgs } from "./args";
2
- import { SocketFunction } from "./SocketFunction";
3
-
4
- //todonext;
5
- // Test the server and client
6
- // - I guess we will want to be able to namespace identifies so we can test
7
- // multiple on the same machine... Let's not use yargs, just argv parsing should be okay?
8
-
9
- class Test {
10
- memberVariable = 5;
11
-
12
- async add(lhs: number, rhs: number) {
13
- return lhs + rhs;
14
- }
15
-
16
- async callMe() {
17
- let caller = TestClass.context.caller?.nodeId;
18
- if (!caller) {
19
- throw new Error("No caller?");
20
- }
21
- console.log(`Caller is ${caller}`);
22
- void (async () => {
23
- let seqNum = 1;
24
- while (true) {
25
- console.log(`Calling client at ${seqNum}`);
26
- await TestClass.nodes[caller].callBack();
27
- await new Promise(resolve => setTimeout(resolve, 1000));
28
- seqNum++;
29
- }
30
- })();
31
- }
32
-
33
- async callBack() {
34
- console.log(`Got callback at ${Date.now()}`);
35
- }
36
- }
37
-
38
- export const TestClass = SocketFunction.register(
39
- "80d9f328-72df-4baa-8be8-019c1003d4a2",
40
- Test,
41
- {
42
- add: {
43
- // hooks: [
44
- // async (config) => {
45
-
46
- // }
47
- // ]
48
- },
49
- callMe: {
50
- clientHooks: [
51
- async (config) => {
52
- config.call.reconnectTimeout = 2000;
53
- }
54
- ]
55
- },
56
- callBack: {
57
-
58
- },
59
- //fncNotAsync: {},
60
- //notAFnc: {},
61
- }
62
- );
63
-
64
- void main();
65
-
66
- //todonext
67
- // - Get case where there will never be a reconnection working
68
-
69
- async function main() {
70
- SocketFunction.expose(Test);
71
- const port = 2542;
72
- if (getArgs().identity === "server") {
73
- await SocketFunction.mount({ port });
74
- } else {
75
- let serverId = await SocketFunction.connect({ port, address: "localhost" });
76
- let test = await TestClass.nodes[serverId].add(1, 2);
77
- console.log(`${test}=${1 + 2}`);
78
-
79
- // while (true) {
80
- // let test = await TestClass.nodes[serverId].add(1, 2);
81
- // console.log(`${test}=${1 + 2}`);
82
- // await new Promise(resolve => setTimeout(resolve, 1000));
83
- // }
84
-
85
- await TestClass.nodes[serverId].callMe();
86
- }
87
- }