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.
Files changed (3) hide show
  1. package/index.js +3 -3
  2. package/lib.js +43 -17
  3. 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 { port, host, apiKey, subdomain } = parseArgs(process.argv.slice(2));
9
+ const { target, host, apiKey, subdomain } = parseArgs(process.argv.slice(2));
10
10
 
11
- console.log(`${BOLD}runlocal${RESET} — expose localhost:${port} to the internet`);
12
- createConnection({ host, port, apiKey, subdomain, WebSocket });
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 port = 3000;
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 localhost to the internet. Works with runlocal.eu");
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
- port = parseInt(argv[i], 10);
88
+ target = parseTarget(argv[i]);
67
89
  }
68
90
  }
69
91
 
70
- return { port, host, apiKey, subdomain };
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, port, nextRef, log) {
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: "127.0.0.1",
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 = http.request(options, (proxyRes) => {
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 localhost:${port} — ${err.message}`,
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
- port,
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 localhost:${port}${RESET}`);
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, port, nextRef, log);
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, port, nextRef, log, activeWsConnections, WebSocket);
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, port, nextRef, log, activeWsConnections, WebSocket) {
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 localWsUrl = `ws://127.0.0.1:${port}${fullPath}`;
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runlocal",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Expose localhost to the internet — open source tunnel server",
5
5
  "bin": {
6
6
  "runlocal": "./index.js"