runlocal 0.4.0 → 0.5.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 +2 -2
- package/lib.js +39 -13
- 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
|
|
9
|
+
const { port, host, apiKey } = parseArgs(process.argv.slice(2));
|
|
10
10
|
|
|
11
11
|
console.log(`${BOLD}runlocal${RESET} — expose localhost:${port} to the internet`);
|
|
12
|
-
createConnection({ host, port, apiKey,
|
|
12
|
+
createConnection({ host, port, apiKey, WebSocket });
|
package/lib.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
const http = require("http");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const os = require("os");
|
|
2
5
|
|
|
3
6
|
const GREEN = "\x1b[32m";
|
|
4
7
|
const CYAN = "\x1b[36m";
|
|
@@ -14,37 +17,50 @@ const TIPS = [
|
|
|
14
17
|
"Forward webhooks to multiple URLs at once with runlater.eu",
|
|
15
18
|
];
|
|
16
19
|
|
|
20
|
+
function readApiKeyFile() {
|
|
21
|
+
try {
|
|
22
|
+
return fs.readFileSync(path.join(os.homedir(), ".runlater", "api-key"), "utf8").trim();
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
function parseArgs(argv) {
|
|
18
29
|
let port = 3000;
|
|
19
30
|
let host = process.env.RUNLOCAL_HOST || "wss://runlocal.eu";
|
|
20
|
-
let apiKey = process.env.RUNLATER_API_KEY ||
|
|
21
|
-
let subdomain = null;
|
|
31
|
+
let apiKey = process.env.RUNLATER_API_KEY || readApiKeyFile();
|
|
22
32
|
for (let i = 0; i < argv.length; i++) {
|
|
23
33
|
if (argv[i] === "--host" && argv[i + 1]) {
|
|
24
34
|
host = argv[++i];
|
|
25
35
|
} else if (argv[i] === "--api-key" && argv[i + 1]) {
|
|
26
36
|
apiKey = argv[++i];
|
|
27
|
-
} else if (argv[i] === "--subdomain" && argv[i + 1]) {
|
|
28
|
-
subdomain = argv[++i];
|
|
29
37
|
} else if (argv[i] === "--help" || argv[i] === "-h") {
|
|
30
38
|
console.log("Usage: runlocal <port> [options]");
|
|
31
39
|
console.log("");
|
|
40
|
+
console.log(" Expose localhost to the internet via runlocal.eu");
|
|
41
|
+
console.log("");
|
|
32
42
|
console.log("Options:");
|
|
33
43
|
console.log(" --host <url> Server URL (default: wss://runlocal.eu)");
|
|
34
44
|
console.log(" --api-key <key> Runlater API key for stable subdomain");
|
|
35
|
-
console.log(" --subdomain <name> Custom subdomain (Pro plan, requires --api-key)");
|
|
36
45
|
console.log(" --help, -h Show this help");
|
|
37
46
|
console.log("");
|
|
38
|
-
console.log("
|
|
39
|
-
console.log("
|
|
40
|
-
console.log(" RUNLATER_API_KEY
|
|
47
|
+
console.log("API key (checked in order):");
|
|
48
|
+
console.log(" 1. --api-key flag");
|
|
49
|
+
console.log(" 2. RUNLATER_API_KEY environment variable");
|
|
50
|
+
console.log(" 3. ~/.runlater/api-key file");
|
|
51
|
+
console.log("");
|
|
52
|
+
console.log("Examples:");
|
|
53
|
+
console.log(" npx runlocal 3000 Random subdomain");
|
|
54
|
+
console.log(" npx runlocal 3000 --api-key pk_xxx Stable subdomain");
|
|
55
|
+
console.log("");
|
|
56
|
+
console.log("A free runlater.eu account gives you a stable subdomain.");
|
|
41
57
|
process.exit(0);
|
|
42
58
|
} else if (!argv[i].startsWith("-")) {
|
|
43
59
|
port = parseInt(argv[i], 10);
|
|
44
60
|
}
|
|
45
61
|
}
|
|
46
62
|
|
|
47
|
-
return { port, host, apiKey
|
|
63
|
+
return { port, host, apiKey };
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
function filterHeaders(headers) {
|
|
@@ -59,10 +75,9 @@ function filterHeaders(headers) {
|
|
|
59
75
|
return filtered;
|
|
60
76
|
}
|
|
61
77
|
|
|
62
|
-
function buildWsUrl(host, apiKey
|
|
78
|
+
function buildWsUrl(host, apiKey) {
|
|
63
79
|
const params = new URLSearchParams({ vsn: "2.0.0" });
|
|
64
80
|
if (apiKey) params.set("api_key", apiKey);
|
|
65
|
-
if (subdomain) params.set("subdomain", subdomain);
|
|
66
81
|
return `${host}/tunnel/websocket?${params.toString()}`;
|
|
67
82
|
}
|
|
68
83
|
|
|
@@ -155,14 +170,13 @@ function createConnection(options) {
|
|
|
155
170
|
host,
|
|
156
171
|
port,
|
|
157
172
|
apiKey,
|
|
158
|
-
subdomain,
|
|
159
173
|
WebSocket,
|
|
160
174
|
onTunnelCreated,
|
|
161
175
|
onClose,
|
|
162
176
|
log = console.log,
|
|
163
177
|
logError = console.error,
|
|
164
178
|
} = options;
|
|
165
|
-
const wsUrl = buildWsUrl(host, apiKey
|
|
179
|
+
const wsUrl = buildWsUrl(host, apiKey);
|
|
166
180
|
|
|
167
181
|
let refCounter = 0;
|
|
168
182
|
const nextRef = () => String(++refCounter);
|
|
@@ -208,6 +222,17 @@ function createConnection(options) {
|
|
|
208
222
|
}
|
|
209
223
|
|
|
210
224
|
if (event === "tunnel_created") {
|
|
225
|
+
const inspectUrl = payload.url.replace(/^https?:\/\/[^/]+/, (origin) => {
|
|
226
|
+
// Convert subdomain URL to main domain /inspect/ URL
|
|
227
|
+
// e.g., https://fuzzy-tiger.runlocal.eu → https://runlocal.eu/inspect/fuzzy-tiger
|
|
228
|
+
const parts = new URL(origin);
|
|
229
|
+
const hostParts = parts.hostname.split(".");
|
|
230
|
+
if (hostParts.length > 2) {
|
|
231
|
+
parts.hostname = hostParts.slice(1).join(".");
|
|
232
|
+
}
|
|
233
|
+
return `${parts.origin}/inspect/${payload.subdomain}`;
|
|
234
|
+
});
|
|
235
|
+
|
|
211
236
|
log("");
|
|
212
237
|
log(` ${GREEN}${BOLD}Tunnel created!${RESET}`);
|
|
213
238
|
log(` ${CYAN}${BOLD}${payload.url}${RESET}`);
|
|
@@ -218,6 +243,7 @@ function createConnection(options) {
|
|
|
218
243
|
|
|
219
244
|
log("");
|
|
220
245
|
log(` ${DIM}Forwarding to localhost:${port}${RESET}`);
|
|
246
|
+
log(` ${DIM}Inspect requests at ${RESET}${CYAN}${inspectUrl}${RESET}`);
|
|
221
247
|
log(` ${DIM}Press Ctrl+C to stop${RESET}`);
|
|
222
248
|
|
|
223
249
|
// Show tip for users without an API key
|