9remote 0.1.34 → 0.1.37

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 CHANGED
@@ -7,10 +7,9 @@ import { spawn, execSync } from "child_process";
7
7
  import path from "path";
8
8
  import { fileURLToPath } from "url";
9
9
  import fs from "fs";
10
- import dns from "dns/promises";
11
10
  import { getConsistentMachineId } from "./utils/machineId.js";
12
11
  import { generateApiKeyWithMachine } from "./utils/apiKey.js";
13
- import { loadKey, saveKey, saveState, clearState } from "./utils/state.js";
12
+ import { loadKey, saveKey, loadState, saveState, clearState } from "./utils/state.js";
14
13
  import { createTempKey } from "./utils/token.js";
15
14
  import { checkAndUpdate } from "./utils/updateChecker.js";
16
15
  import { ensureCloudflared, spawnCloudflared, killCloudflared, resetRestartCounter } from "./utils/cloudflared.js";
@@ -26,6 +25,7 @@ const STANDALONE_SERVER = path.join(__dirname, "server.cjs");
26
25
  const DEV_SERVER = path.join(PROJECT_ROOT, "server/index.js");
27
26
  const WORKER_URL = "https://9remote.cc";
28
27
  const SERVER_PORT = 2208;
28
+ const SHORT_ID_CHARS = "abcdefghijklmnpqrstuvwxyz23456789";
29
29
  const MAX_RESTART_ATTEMPTS = 10;
30
30
  const RESTART_WINDOW_MS = 60000; // 1 minute
31
31
 
@@ -88,6 +88,17 @@ function showBanner() {
88
88
  console.log("");
89
89
  }
90
90
 
91
+ /**
92
+ * Generate short random ID for tunnel subdomain
93
+ */
94
+ function generateShortId() {
95
+ let result = "";
96
+ for (let i = 0; i < 6; i++) {
97
+ result += SHORT_ID_CHARS.charAt(Math.floor(Math.random() * SHORT_ID_CHARS.length));
98
+ }
99
+ return result;
100
+ }
101
+
91
102
  /**
92
103
  * Helper: Show QR code for connect URL
93
104
  */
@@ -305,12 +316,16 @@ async function startServerAndTunnel(selectedKey) {
305
316
  await new Promise(resolve => setTimeout(resolve, 1000));
306
317
  } catch { }
307
318
 
319
+ // Reuse existing shortId or generate new one
320
+ const existingState = loadState();
321
+ const shortId = existingState?.shortId || generateShortId();
322
+
308
323
  // Create session first
309
324
  try {
310
325
  const sessionResponse = await fetch(`${WORKER_URL}/api/session/create`, {
311
326
  method: "POST",
312
327
  headers: { "Content-Type": "application/json" },
313
- body: JSON.stringify({ apiKey: selectedKey })
328
+ body: JSON.stringify({ apiKey: selectedKey, shortId })
314
329
  });
315
330
 
316
331
  if (!sessionResponse.ok) {
@@ -435,65 +450,13 @@ async function startServerAndTunnel(selectedKey) {
435
450
  }
436
451
 
437
452
 
438
- // Wait for tunnel to be ready
439
453
  console.log(ORANGE(`✅ Tunnel URL: ${tunnelUrl}`));
440
-
441
- const maxWaitTime = 60000;
442
- const checkInterval = 2000;
443
- const maxRetries = Math.floor(maxWaitTime / checkInterval);
444
- let tunnelReady = false;
445
-
446
- // Resolve IP using Cloudflare DNS to bypass local cache
447
- const hostname = new URL(tunnelUrl).hostname;
448
- let resolvedIp = null;
449
-
450
- for (let i = 0; i < maxRetries; i++) {
451
- try {
452
- // Resolve DNS using Cloudflare DNS server (bypass local cache)
453
- if (!resolvedIp) {
454
- const resolver = new dns.Resolver();
455
- resolver.setServers(["1.1.1.1", "1.0.0.1"]);
456
- const addresses = await resolver.resolve4(hostname);
457
- if (addresses.length > 0) {
458
- resolvedIp = addresses[0];
459
- }
460
- }
461
-
462
- if (resolvedIp) {
463
- // Use curl with --resolve to bypass DNS cache and SSL issues
464
- const curlResult = execSync(
465
- `curl -s --max-time 5 --resolve "${hostname}:443:${resolvedIp}" "${tunnelUrl}/api/health"`,
466
- { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
467
- );
468
- if (curlResult.includes("ok")) {
469
- tunnelReady = true;
470
- break;
471
- } else {
472
- }
473
- }
474
- } catch (err) {
475
- }
476
-
477
- const spinners = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
478
- process.stdout.write(`\r${ORANGE(" Waiting for tunnel")} ${ORANGE(spinners[i % spinners.length])} ${chalk.gray(`(${i * 2}s)`)}`);
479
-
480
- await new Promise(r => setTimeout(r, checkInterval));
481
- }
482
-
483
- process.stdout.write("\r" + " ".repeat(50) + "\r");
484
-
485
- if (!tunnelReady) {
486
- console.log(chalk.red("❌ Tunnel connection timeout"));
487
- serverManager.shutdown();
488
- tunnelProcess.kill();
489
- return null;
490
- }
491
-
492
454
  console.log(ORANGE(`✅ Connection established`));
493
455
 
494
- // Save state
456
+ // Save state (persist shortId for reuse on restart)
495
457
  saveState({
496
458
  apiKey: selectedKey,
459
+ shortId,
497
460
  tunnelUrl,
498
461
  serverPid: serverManager.getProcess()?.pid,
499
462
  tunnelPid: tunnelProcess.pid
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "9remote",
3
- "version": "0.1.34",
3
+ "version": "0.1.37",
4
4
  "type": "module",
5
5
  "description": "Remote terminal access from anywhere",
6
6
  "main": "index.js",
@@ -183,44 +183,40 @@ export async function spawnCloudflared(tunnelToken, onRestart = null) {
183
183
  stdio: ["ignore", "pipe", "pipe"]
184
184
  });
185
185
 
186
- // Reset intentional shutdown flag immediately after spawn
187
- // This ensures auto-restart works if cloudflared crashes
188
186
  isIntentionalShutdown = false;
189
187
  console.log(`✅ Cloudflared spawned with PID: ${child.pid}`);
190
188
 
191
- let connectionCount = 0;
192
-
193
- const handleLog = (data) => {
194
- const msg = data.toString().trim();
195
-
196
- // Skip ignored messages
197
- if (LOG_IGNORE.some(pattern => msg.includes(pattern))) {
198
- return;
199
- }
200
-
201
- // Skip errors during intentional shutdown
202
- if (isIntentionalShutdown) {
203
- return;
204
- }
205
-
206
- // Show connection status briefly
207
- if (msg.includes("Registered tunnel connection")) {
208
- connectionCount++;
209
- if (connectionCount <= 4) {
210
- process.stdout.write(`\r ✔ Connection ${connectionCount}/4 established`);
211
- if (connectionCount === 4) {
212
- process.stdout.write("\n");
189
+ // Wait for 4 connections before resolving (tunnel is truly ready)
190
+ await new Promise((resolve, reject) => {
191
+ let connectionCount = 0;
192
+ let resolved = false;
193
+ const timeout = setTimeout(() => {
194
+ if (!resolved) { resolved = true; resolve(child); }
195
+ }, 90000);
196
+
197
+ const handleLog = (data) => {
198
+ const msg = data.toString().trim();
199
+ if (LOG_IGNORE.some(pattern => msg.includes(pattern))) return;
200
+ if (isIntentionalShutdown) return;
201
+ if (msg.includes("Registered tunnel connection")) {
202
+ connectionCount++;
203
+ if (connectionCount <= 4) {
204
+ process.stdout.write(`\r ✔ Connection ${connectionCount}/4 established`);
205
+ if (connectionCount === 4) {
206
+ process.stdout.write("\n");
207
+ if (!resolved) { resolved = true; clearTimeout(timeout); resolve(child); }
208
+ }
213
209
  }
210
+ return;
214
211
  }
215
- return;
216
- }
217
-
218
- // Suppress all other cloudflared logs
219
- };
220
-
221
- child.stdout.on("data", handleLog);
222
- child.stderr.on("data", handleLog);
223
-
212
+ };
213
+
214
+ child.stdout.on("data", handleLog);
215
+ child.stderr.on("data", handleLog);
216
+ child.on("error", (err) => { if (!resolved) { resolved = true; clearTimeout(timeout); reject(err); } });
217
+ child.on("exit", (code) => { if (!resolved) { resolved = true; clearTimeout(timeout); reject(new Error(`cloudflared exited with code ${code}`)); } });
218
+ });
219
+
224
220
  child.on("error", (error) => {
225
221
  console.error("❌ cloudflared error:", error);
226
222
  });