lunel-cli 0.1.93 → 0.1.95

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/dist/index.js CHANGED
@@ -251,7 +251,7 @@ function parseExtraPortsFromArgs(args) {
251
251
  const EXTRA_PORTS = parseExtraPortsFromArgs(CLI_ARGS);
252
252
  const USE_APPLE_REVIEW_CODE = hasAnyFlag(CLI_ARGS, "--abcd-code");
253
253
  const FORCE_NEW_CODE = hasAnyFlag(CLI_ARGS, "--new", "-n");
254
- const SCAN_PORTS = Array.from(new Set([...DEV_PORTS, ...EXTRA_PORTS])).sort((a, b) => a - b);
254
+ const trackedProxyPorts = new Set(EXTRA_PORTS);
255
255
  function samePortSet(a, b) {
256
256
  if (a.length !== b.length)
257
257
  return false;
@@ -1994,7 +1994,8 @@ async function handleHttpRequest(payload) {
1994
1994
  // ============================================================================
1995
1995
  async function scanDevPorts() {
1996
1996
  const openPorts = [];
1997
- const checks = SCAN_PORTS.map((port) => {
1997
+ const scanPorts = Array.from(new Set([...DEV_PORTS, ...trackedProxyPorts])).sort((a, b) => a - b);
1998
+ const checks = scanPorts.map((port) => {
1998
1999
  return new Promise((resolve) => {
1999
2000
  let finished = false;
2000
2001
  let pending = LOOPBACK_HOSTS.length;
@@ -2031,6 +2032,9 @@ async function scanDevPorts() {
2031
2032
  await Promise.all(checks);
2032
2033
  return openPorts.sort((a, b) => a - b);
2033
2034
  }
2035
+ function getTrackedProxyPorts() {
2036
+ return Array.from(trackedProxyPorts).sort((a, b) => a - b);
2037
+ }
2034
2038
  async function publishDiscoveredPorts(force = false) {
2035
2039
  if (portScanInFlight)
2036
2040
  return;
@@ -2059,6 +2063,46 @@ async function publishDiscoveredPorts(force = false) {
2059
2063
  portScanInFlight = false;
2060
2064
  }
2061
2065
  }
2066
+ async function getProxyState() {
2067
+ const openPorts = await scanDevPorts();
2068
+ lastDiscoveredPorts = openPorts;
2069
+ return {
2070
+ trackedPorts: getTrackedProxyPorts(),
2071
+ openPorts,
2072
+ };
2073
+ }
2074
+ async function handleTrackProxyPort(payload) {
2075
+ const port = Number(payload.port);
2076
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
2077
+ throw Object.assign(new Error("port must be an integer between 1 and 65535"), { code: "EINVAL" });
2078
+ }
2079
+ trackedProxyPorts.add(port);
2080
+ debugLog("[proxy] tracking custom port", {
2081
+ port,
2082
+ trackedPorts: getTrackedProxyPorts(),
2083
+ });
2084
+ await publishDiscoveredPorts(true);
2085
+ return {
2086
+ trackedPorts: getTrackedProxyPorts(),
2087
+ openPorts: lastDiscoveredPorts,
2088
+ };
2089
+ }
2090
+ async function handleUntrackProxyPort(payload) {
2091
+ const port = Number(payload.port);
2092
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
2093
+ throw Object.assign(new Error("port must be an integer between 1 and 65535"), { code: "EINVAL" });
2094
+ }
2095
+ trackedProxyPorts.delete(port);
2096
+ debugLog("[proxy] removed custom port tracking", {
2097
+ port,
2098
+ trackedPorts: getTrackedProxyPorts(),
2099
+ });
2100
+ await publishDiscoveredPorts(true);
2101
+ return {
2102
+ trackedPorts: getTrackedProxyPorts(),
2103
+ openPorts: lastDiscoveredPorts,
2104
+ };
2105
+ }
2062
2106
  function stopPortSync() {
2063
2107
  if (portSyncTimer) {
2064
2108
  clearInterval(portSyncTimer);
@@ -2078,6 +2122,13 @@ async function handleProxyConnect(payload) {
2078
2122
  const port = payload.port;
2079
2123
  const setupStartedAt = Date.now();
2080
2124
  const getRemainingSetupMs = () => TUNNEL_SETUP_BUDGET_MS - (Date.now() - setupStartedAt);
2125
+ debugLog("[proxy] handleProxyConnect received", {
2126
+ tunnelId,
2127
+ port,
2128
+ hasSessionCode: Boolean(currentSessionCode),
2129
+ hasSessionPassword: Boolean(currentSessionPassword),
2130
+ activeGatewayUrl,
2131
+ });
2081
2132
  if (!tunnelId)
2082
2133
  throw Object.assign(new Error("tunnelId is required"), { code: "EINVAL" });
2083
2134
  if (!port)
@@ -2126,8 +2177,14 @@ async function handleProxyConnect(payload) {
2126
2177
  }
2127
2178
  }
2128
2179
  if (!tcpSocket) {
2180
+ debugLog("[proxy] local tcp connect failed", {
2181
+ tunnelId,
2182
+ port,
2183
+ error: tcpConnectError?.message ?? null,
2184
+ });
2129
2185
  throw tcpConnectError || Object.assign(new Error(`TCP connect failed to localhost:${port}`), { code: "ECONNREFUSED" });
2130
2186
  }
2187
+ debugLog("[proxy] local tcp connected", { tunnelId, port });
2131
2188
  // 2. Open proxy WebSocket to gateway
2132
2189
  const wsBase = activeGatewayUrl.replace(/^https:/, "wss:");
2133
2190
  if (!wsBase.startsWith("wss://")) {
@@ -2137,6 +2194,12 @@ async function handleProxyConnect(payload) {
2137
2194
  ? `password=${encodeURIComponent(currentSessionPassword)}`
2138
2195
  : `code=${encodeURIComponent(currentSessionCode)}`;
2139
2196
  const proxyWsUrl = `${wsBase}/v1/ws/proxy?${authQuery}&tunnelId=${encodeURIComponent(tunnelId)}&role=cli`;
2197
+ debugLog("[proxy] connecting cli proxy websocket", {
2198
+ tunnelId,
2199
+ port,
2200
+ authMode: currentSessionPassword ? "password" : "code",
2201
+ wsBase,
2202
+ });
2140
2203
  let proxyWs = null;
2141
2204
  let lastProxyError = null;
2142
2205
  for (let attempt = 0; attempt <= PROXY_WS_CONNECT_RETRY_ATTEMPTS; attempt++) {
@@ -2191,8 +2254,14 @@ async function handleProxyConnect(payload) {
2191
2254
  if (!proxyWs) {
2192
2255
  tcpSocket.destroy();
2193
2256
  const err = lastProxyError || Object.assign(new Error("Proxy WS connect failed"), { code: "ECONNREFUSED" });
2257
+ debugLog("[proxy] cli proxy websocket connect failed", {
2258
+ tunnelId,
2259
+ port,
2260
+ error: err.message,
2261
+ });
2194
2262
  throw err;
2195
2263
  }
2264
+ debugLog("[proxy] cli proxy websocket connected", { tunnelId, port });
2196
2265
  // 3. Store the tunnel
2197
2266
  activeTunnels.set(tunnelId, {
2198
2267
  tunnelId,
@@ -2267,6 +2336,7 @@ async function handleProxyConnect(payload) {
2267
2336
  markLocalEnded();
2268
2337
  });
2269
2338
  tcpSocket.on("error", () => {
2339
+ debugLog("[proxy] local tcp socket error", { tunnelId, port });
2270
2340
  const tunnel = activeTunnels.get(tunnelId);
2271
2341
  if (tunnel && !tunnel.finSent) {
2272
2342
  sendProxyControl(tunnel, "rst", "tcp_error");
@@ -2284,6 +2354,7 @@ async function handleProxyConnect(payload) {
2284
2354
  });
2285
2355
  // 7. Close cascade: WS closes -> close TCP
2286
2356
  proxyWs.on("close", () => {
2357
+ debugLog("[proxy] cli proxy websocket closed", { tunnelId, port });
2287
2358
  const tunnel = activeTunnels.get(tunnelId);
2288
2359
  if (tunnel) {
2289
2360
  tunnel.closing = true;
@@ -2297,6 +2368,7 @@ async function handleProxyConnect(payload) {
2297
2368
  }
2298
2369
  });
2299
2370
  proxyWs.on("error", () => {
2371
+ debugLog("[proxy] cli proxy websocket error", { tunnelId, port });
2300
2372
  const tunnel = activeTunnels.get(tunnelId);
2301
2373
  if (tunnel) {
2302
2374
  tunnel.closing = true;
@@ -2363,6 +2435,7 @@ async function processMessage(message) {
2363
2435
  switch (action) {
2364
2436
  case "capabilities":
2365
2437
  result = handleSystemCapabilities();
2438
+ void publishDiscoveredPorts(true);
2366
2439
  break;
2367
2440
  case "ping":
2368
2441
  result = handleSystemPing();
@@ -2626,6 +2699,15 @@ async function processMessage(message) {
2626
2699
  case "connect":
2627
2700
  result = await handleProxyConnect(payload);
2628
2701
  break;
2702
+ case "getState":
2703
+ result = await getProxyState();
2704
+ break;
2705
+ case "trackPort":
2706
+ result = await handleTrackProxyPort(payload);
2707
+ break;
2708
+ case "untrackPort":
2709
+ result = await handleUntrackProxyPort(payload);
2710
+ break;
2629
2711
  default:
2630
2712
  throw Object.assign(new Error(`Unknown action: ${ns}.${action}`), { code: "EINVAL" });
2631
2713
  }
@@ -2836,15 +2918,12 @@ async function connectWebSocketV2() {
2836
2918
  if (!currentSessionPassword) {
2837
2919
  throw new Error("missing password for websocket connect");
2838
2920
  }
2839
- if (!currentSessionCode) {
2840
- throw new Error("missing session code for secure transport");
2841
- }
2842
2921
  console.log(`Connecting to gateway ${gatewayUrl}...`);
2843
2922
  activeGatewayUrl = gatewayUrl;
2844
2923
  const transport = new V2SessionTransport({
2845
2924
  gatewayUrl,
2846
2925
  password: currentSessionPassword,
2847
- sessionCode: currentSessionCode,
2926
+ sessionSecret: currentSessionPassword,
2848
2927
  role: "cli",
2849
2928
  debugLog: DEBUG_MODE ? debugLog : undefined,
2850
2929
  handlers: {
@@ -2853,6 +2932,7 @@ async function connectWebSocketV2() {
2853
2932
  return;
2854
2933
  if (message.type === "peer_connected") {
2855
2934
  console.log("App connected!\n");
2935
+ void publishDiscoveredPorts(true);
2856
2936
  return;
2857
2937
  }
2858
2938
  if (message.type === "peer_disconnected") {
@@ -9,7 +9,7 @@ export interface V2TransportHandlers {
9
9
  export interface V2TransportOptions {
10
10
  gatewayUrl: string;
11
11
  password: string;
12
- sessionCode: string;
12
+ sessionSecret: string;
13
13
  role: "cli" | "app";
14
14
  handlers: V2TransportHandlers;
15
15
  debugLog?: (message: string, ...args: unknown[]) => void;
@@ -127,6 +127,9 @@ export class V2SessionTransport {
127
127
  this.resetPeerSession();
128
128
  await this.maybeStartHandshake();
129
129
  }
130
+ else if (raw.type === "peer_disconnected" || raw.type === "app_disconnected") {
131
+ this.resetPeerSession();
132
+ }
130
133
  return;
131
134
  }
132
135
  if (isV2HandshakeFrame(raw)) {
@@ -302,7 +305,7 @@ export class V2SessionTransport {
302
305
  }
303
306
  }
304
307
  computeHandshakeAuth(phase, senderRole, peerPubkeyB64, nonce, boxed) {
305
- const authKey = sodium.crypto_generichash(sodium.crypto_auth_KEYBYTES, encodeUtf8(this.options.sessionCode), undefined);
308
+ const authKey = sodium.crypto_generichash(sodium.crypto_auth_KEYBYTES, encodeUtf8(this.options.sessionSecret), undefined);
306
309
  const parts = [
307
310
  phase,
308
311
  senderRole,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunel-cli",
3
- "version": "0.1.93",
3
+ "version": "0.1.95",
4
4
  "author": [
5
5
  {
6
6
  "name": "Soham Bharambe",