runlocal 0.8.0 → 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/index.js +3 -3
- package/lib.js +43 -17
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -6,7 +6,7 @@ const { parseArgs, createConnection } = require("./lib");
|
|
|
6
6
|
const BOLD = "\x1b[1m";
|
|
7
7
|
const RESET = "\x1b[0m";
|
|
8
8
|
|
|
9
|
-
const {
|
|
9
|
+
const { target, host, apiKey, subdomain } = parseArgs(process.argv.slice(2));
|
|
10
10
|
|
|
11
|
-
console.log(`${BOLD}runlocal${RESET} — expose
|
|
12
|
-
createConnection({ host,
|
|
11
|
+
console.log(`${BOLD}runlocal${RESET} — expose ${target.display} to the internet`);
|
|
12
|
+
createConnection({ host, target, apiKey, subdomain, WebSocket });
|
package/lib.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const http = require("http");
|
|
2
|
+
const https = require("https");
|
|
2
3
|
const fs = require("fs");
|
|
3
4
|
const path = require("path");
|
|
4
5
|
const os = require("os");
|
|
@@ -25,8 +26,27 @@ function readApiKeyFile() {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
function parseTarget(value) {
|
|
30
|
+
if (/^https?:\/\//.test(value)) {
|
|
31
|
+
const url = new URL(value);
|
|
32
|
+
return {
|
|
33
|
+
hostname: url.hostname,
|
|
34
|
+
port: url.port ? parseInt(url.port, 10) : (url.protocol === "https:" ? 443 : 80),
|
|
35
|
+
protocol: url.protocol,
|
|
36
|
+
display: value.replace(/\/$/, ""),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const port = parseInt(value, 10);
|
|
40
|
+
return {
|
|
41
|
+
hostname: "127.0.0.1",
|
|
42
|
+
port,
|
|
43
|
+
protocol: "http:",
|
|
44
|
+
display: `localhost:${port}`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
function parseArgs(argv) {
|
|
29
|
-
let
|
|
49
|
+
let target = parseTarget("3000");
|
|
30
50
|
let host = process.env.RUNLOCAL_HOST || "wss://runlocal.eu";
|
|
31
51
|
let apiKey = process.env.RUNLATER_API_KEY || readApiKeyFile();
|
|
32
52
|
let subdomain = null;
|
|
@@ -38,9 +58,9 @@ function parseArgs(argv) {
|
|
|
38
58
|
} else if (argv[i] === "--subdomain" && argv[i + 1]) {
|
|
39
59
|
subdomain = argv[++i];
|
|
40
60
|
} else if (argv[i] === "--help" || argv[i] === "-h") {
|
|
41
|
-
console.log("Usage: runlocal <port> [options]");
|
|
61
|
+
console.log("Usage: runlocal <port|url> [options]");
|
|
42
62
|
console.log("");
|
|
43
|
-
console.log(" Expose
|
|
63
|
+
console.log(" Expose a local server to the internet. Works with runlocal.eu");
|
|
44
64
|
console.log(" or any self-hosted runlocal server.");
|
|
45
65
|
console.log("");
|
|
46
66
|
console.log("Options:");
|
|
@@ -55,6 +75,8 @@ function parseArgs(argv) {
|
|
|
55
75
|
console.log("");
|
|
56
76
|
console.log("Examples:");
|
|
57
77
|
console.log(" npx runlocal 3000 Random subdomain");
|
|
78
|
+
console.log(" npx runlocal https://10.8.0.1 Proxy any URL");
|
|
79
|
+
console.log(" npx runlocal http://myapp.local:8080 Custom host and port");
|
|
58
80
|
console.log(" npx runlocal 3000 --api-key pk_xxx Stable subdomain");
|
|
59
81
|
console.log(" npx runlocal 3000 --subdomain my-api Custom subdomain");
|
|
60
82
|
console.log(" npx runlocal 3000 --server wss://tunnel.example.com Self-hosted");
|
|
@@ -63,11 +85,11 @@ function parseArgs(argv) {
|
|
|
63
85
|
console.log("Hosted version: https://runlocal.eu");
|
|
64
86
|
process.exit(0);
|
|
65
87
|
} else if (!argv[i].startsWith("-")) {
|
|
66
|
-
|
|
88
|
+
target = parseTarget(argv[i]);
|
|
67
89
|
}
|
|
68
90
|
}
|
|
69
91
|
|
|
70
|
-
return {
|
|
92
|
+
return { target, host, apiKey, subdomain };
|
|
71
93
|
}
|
|
72
94
|
|
|
73
95
|
function filterHeaders(headers) {
|
|
@@ -89,7 +111,7 @@ function buildWsUrl(host, apiKey, subdomain) {
|
|
|
89
111
|
return `${host}/tunnel/websocket?${params.toString()}`;
|
|
90
112
|
}
|
|
91
113
|
|
|
92
|
-
function handleRequest(ws, joinRef, topic, payload,
|
|
114
|
+
function handleRequest(ws, joinRef, topic, payload, target, nextRef, log) {
|
|
93
115
|
const { request_id, method, path, query_string, headers, body } = payload;
|
|
94
116
|
const fullPath = query_string ? `${path}?${query_string}` : path;
|
|
95
117
|
|
|
@@ -100,15 +122,17 @@ function handleRequest(ws, joinRef, topic, payload, port, nextRef, log) {
|
|
|
100
122
|
|
|
101
123
|
const reqHeaders = filterHeaders(headers);
|
|
102
124
|
|
|
125
|
+
const requester = target.protocol === "https:" ? https : http;
|
|
103
126
|
const options = {
|
|
104
|
-
hostname:
|
|
105
|
-
port: port,
|
|
127
|
+
hostname: target.hostname,
|
|
128
|
+
port: target.port,
|
|
106
129
|
path: fullPath,
|
|
107
130
|
method: method,
|
|
108
131
|
headers: reqHeaders,
|
|
132
|
+
rejectUnauthorized: false,
|
|
109
133
|
};
|
|
110
134
|
|
|
111
|
-
const proxyReq =
|
|
135
|
+
const proxyReq = requester.request(options, (proxyRes) => {
|
|
112
136
|
const chunks = [];
|
|
113
137
|
proxyRes.on("data", (chunk) => chunks.push(chunk));
|
|
114
138
|
proxyRes.on("end", () => {
|
|
@@ -161,7 +185,7 @@ function handleRequest(ws, joinRef, topic, payload, port, nextRef, log) {
|
|
|
161
185
|
request_id,
|
|
162
186
|
status: 502,
|
|
163
187
|
headers: [["content-type", "text/plain"]],
|
|
164
|
-
body: `Could not connect to
|
|
188
|
+
body: `Could not connect to ${target.display} — ${err.message}`,
|
|
165
189
|
},
|
|
166
190
|
])
|
|
167
191
|
);
|
|
@@ -176,7 +200,7 @@ function handleRequest(ws, joinRef, topic, payload, port, nextRef, log) {
|
|
|
176
200
|
function createConnection(options) {
|
|
177
201
|
const {
|
|
178
202
|
host,
|
|
179
|
-
|
|
203
|
+
target,
|
|
180
204
|
apiKey,
|
|
181
205
|
subdomain,
|
|
182
206
|
WebSocket,
|
|
@@ -252,7 +276,7 @@ function createConnection(options) {
|
|
|
252
276
|
}
|
|
253
277
|
|
|
254
278
|
log("");
|
|
255
|
-
log(` ${DIM}Forwarding to
|
|
279
|
+
log(` ${DIM}Forwarding to ${target.display}${RESET}`);
|
|
256
280
|
log(` ${DIM}Inspect requests at ${RESET}${CYAN}${inspectUrl}${RESET}`);
|
|
257
281
|
log(` ${DIM}Press Ctrl+C to stop${RESET}`);
|
|
258
282
|
|
|
@@ -268,12 +292,12 @@ function createConnection(options) {
|
|
|
268
292
|
}
|
|
269
293
|
|
|
270
294
|
if (event === "http_request") {
|
|
271
|
-
handleRequest(ws, joinRef, topic, payload,
|
|
295
|
+
handleRequest(ws, joinRef, topic, payload, target, nextRef, log);
|
|
272
296
|
return;
|
|
273
297
|
}
|
|
274
298
|
|
|
275
299
|
if (event === "ws_upgrade") {
|
|
276
|
-
handleWsUpgrade(ws, joinRef, topic, payload,
|
|
300
|
+
handleWsUpgrade(ws, joinRef, topic, payload, target, nextRef, log, activeWsConnections, WebSocket);
|
|
277
301
|
return;
|
|
278
302
|
}
|
|
279
303
|
|
|
@@ -322,14 +346,15 @@ function createConnection(options) {
|
|
|
322
346
|
return { ws, getJoinRef: () => joinRef, nextRef };
|
|
323
347
|
}
|
|
324
348
|
|
|
325
|
-
function handleWsUpgrade(ws, joinRef, topic, payload,
|
|
349
|
+
function handleWsUpgrade(ws, joinRef, topic, payload, target, nextRef, log, activeWsConnections, WebSocket) {
|
|
326
350
|
const { ws_id, path: wsPath, query_string, headers } = payload;
|
|
327
351
|
const fullPath = query_string ? `${wsPath}?${query_string}` : wsPath;
|
|
328
352
|
const timestamp = new Date().toLocaleTimeString();
|
|
329
353
|
|
|
330
354
|
log(`${DIM}${timestamp}${RESET} ${BOLD}WS${RESET} ${fullPath}`);
|
|
331
355
|
|
|
332
|
-
const
|
|
356
|
+
const wsProtocol = target.protocol === "https:" ? "wss:" : "ws:";
|
|
357
|
+
const localWsUrl = `${wsProtocol}//${target.hostname}:${target.port}${fullPath}`;
|
|
333
358
|
|
|
334
359
|
const reqHeaders = {};
|
|
335
360
|
if (headers) {
|
|
@@ -345,7 +370,7 @@ function handleWsUpgrade(ws, joinRef, topic, payload, port, nextRef, log, active
|
|
|
345
370
|
|
|
346
371
|
let localWs;
|
|
347
372
|
try {
|
|
348
|
-
localWs = new WebSocket(localWsUrl, { headers: reqHeaders });
|
|
373
|
+
localWs = new WebSocket(localWsUrl, { headers: reqHeaders, rejectUnauthorized: false });
|
|
349
374
|
} catch (err) {
|
|
350
375
|
log(`${DIM}${timestamp}${RESET} ${RED}WS ERR${RESET} ${fullPath} — ${err.message}`);
|
|
351
376
|
ws.send(JSON.stringify([joinRef, nextRef(), topic, "ws_close", { ws_id }]));
|
|
@@ -403,6 +428,7 @@ function handleWsClose(payload, activeWsConnections) {
|
|
|
403
428
|
|
|
404
429
|
module.exports = {
|
|
405
430
|
parseArgs,
|
|
431
|
+
parseTarget,
|
|
406
432
|
filterHeaders,
|
|
407
433
|
buildWsUrl,
|
|
408
434
|
handleRequest,
|