@miraj181/ipingyou 2.1.5 → 2.1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraj181/ipingyou",
3
- "version": "2.1.5",
3
+ "version": "2.1.6",
4
4
  "description": "SecureLink-CLI — Secure peer-to-peer remote access via SSH & Cloudflare Tunnels",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -14,7 +14,7 @@ import fs from 'node:fs';
14
14
  import os from 'node:os';
15
15
  import path from 'node:path';
16
16
  import { execaCommand } from 'execa';
17
- import { TMUX_SESSION_NAME, tmuxSocketArgs } from './tmux.js';
17
+ import { TMUX_SESSION_NAME, TMUX_SESSION_PREFIX, tmuxSocketArgs } from './tmux.js';
18
18
 
19
19
  /** @type {Set<number>} — Active child PIDs we manage */
20
20
  const trackedPIDs = new Set();
@@ -192,7 +192,15 @@ export async function executePanicMode() {
192
192
  } else {
193
193
  await execaCommand('pkill -9 -f cloudflared', { reject: false });
194
194
  await execaCommand('pkill -9 -f "sshd:.*@"', { reject: false });
195
- await execaCommand(`tmux ${tmuxSocketArgs().join(' ')} kill-session -t ${TMUX_SESSION_NAME}`, { reject: false });
195
+ await execaCommand(`tmux ${tmuxSocketArgs().join(' ')} kill-server`, { reject: false });
196
+ const { stdout } = await execaCommand('tmux list-sessions -F "#{session_name}"', { reject: false });
197
+ const legacyNames = stdout
198
+ .split(/\r?\n/)
199
+ .filter(Boolean)
200
+ .filter(name => name === TMUX_SESSION_NAME || name.startsWith(TMUX_SESSION_PREFIX));
201
+ for (const name of legacyNames) {
202
+ await execaCommand(`tmux kill-session -t ${name}`, { reject: false });
203
+ }
196
204
  }
197
205
  } catch {}
198
206
 
package/src/lib/tmux.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import os from 'node:os';
2
2
  import path from 'node:path';
3
3
 
4
+ export const TMUX_SESSION_PREFIX = 'SecureLink_';
4
5
  export const TMUX_SESSION_NAME = 'SecureLink_Session';
5
6
  export const TMUX_SOCKET_PATH = path.join(os.tmpdir(), 'ipingyou-tmux.sock');
6
7
 
@@ -11,3 +12,12 @@ export function tmuxSocketArgs() {
11
12
  export function tmuxSocketCommand() {
12
13
  return `tmux -S ${TMUX_SOCKET_PATH}`;
13
14
  }
15
+
16
+ export function buildTmuxSessionName(label) {
17
+ const safeLabel = String(label || 'client')
18
+ .replace(/[^a-zA-Z0-9_-]/g, '')
19
+ .slice(0, 24) || 'client';
20
+ const stamp = Date.now().toString(36);
21
+ const rand = Math.random().toString(36).slice(2, 6);
22
+ return `${TMUX_SESSION_PREFIX}${safeLabel}_${stamp}${rand}`;
23
+ }
@@ -25,7 +25,7 @@ import { pushTelemetry, requestHostApproval, resolveUID, revokeUID, waitForAppro
25
25
  import { calculateChecksum } from '../lib/checksum.js';
26
26
  import { promptLocalPath, promptRemotePath } from '../lib/path-browser.js';
27
27
  import { buildSshArgs, extractHostname, formatScpRemotePath, getKnownHostsOptions, getSshControlOptions, quoteRemoteShell } from '../lib/ssh.js';
28
- import { TMUX_SESSION_NAME, tmuxSocketCommand } from '../lib/tmux.js';
28
+ import { buildTmuxSessionName, tmuxSocketCommand } from '../lib/tmux.js';
29
29
  import open from 'open';
30
30
  import { cleanupSessionLog, initSessionLog, logSessionEvent, recordEvent } from '../lib/session-log.js';
31
31
 
@@ -88,8 +88,9 @@ async function connectSSH(username, hostname, privateKeyPath, persistKnownHosts
88
88
  ], { persistKnownHosts });
89
89
 
90
90
  sshArgs.push(`${username}@${hostname}`);
91
- const tmuxPrepare = `${tmuxSocketCommand()} has-session -t ${TMUX_SESSION_NAME} 2>/dev/null || ${tmuxSocketCommand()} new-session -d -s ${TMUX_SESSION_NAME}`;
92
- const tmuxAttach = `${tmuxSocketCommand()} attach -t ${TMUX_SESSION_NAME}`;
91
+ const tmuxSession = buildTmuxSessionName(username);
92
+ const tmuxPrepare = `${tmuxSocketCommand()} has-session -t ${tmuxSession} 2>/dev/null || ${tmuxSocketCommand()} new-session -d -s ${tmuxSession}`;
93
+ const tmuxAttach = `${tmuxSocketCommand()} attach -t ${tmuxSession}`;
93
94
  sshArgs.push('-t', `${tmuxPrepare} && ${tmuxAttach} || exec $SHELL -l`);
94
95
 
95
96
  const child = execa('ssh', sshArgs, {
package/src/modes/host.js CHANGED
@@ -29,7 +29,7 @@ import { startChatServer, openLocalChatUI } from '../lib/chat.js';
29
29
  import { spawnTunnelSupervised } from '../lib/tunnel.js';
30
30
  import { decideApprovalRequest, fetchApprovalRequests, pingBroker, registerWithBroker, revokeUID } from '../lib/broker.js';
31
31
  import { cleanupSessionLog, getSessionLogPath, initSessionLog, logSessionEvent, recordEvent } from '../lib/session-log.js';
32
- import { TMUX_SESSION_NAME, tmuxSocketArgs } from '../lib/tmux.js';
32
+ import { TMUX_SESSION_NAME, TMUX_SESSION_PREFIX, tmuxSocketArgs } from '../lib/tmux.js';
33
33
 
34
34
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
35
35
  let BROKER_URL = process.env.BROKER_URL || 'https://ipingyou.onrender.com';
@@ -139,6 +139,37 @@ async function ensureTmuxInstalled() {
139
139
  throw new Error('Homebrew is required to install tmux on macOS');
140
140
  }
141
141
  }
142
+
143
+ function isSecureLinkSession(name) {
144
+ return name === TMUX_SESSION_NAME || name.startsWith(TMUX_SESSION_PREFIX);
145
+ }
146
+
147
+ async function listTmuxSessions(socketArgs = []) {
148
+ const result = await execa('tmux', [...socketArgs, 'list-sessions', '-F', '#{session_name}|#{session_created}'], { reject: false });
149
+ if (result.exitCode !== 0) return [];
150
+ return result.stdout
151
+ .split(/\r?\n/)
152
+ .filter(Boolean)
153
+ .map(line => {
154
+ const [name, createdAt] = line.split('|');
155
+ return { name, createdAt: Number(createdAt) || null };
156
+ });
157
+ }
158
+
159
+ async function getMirrorableSessions() {
160
+ const sessions = [];
161
+ const customSessions = await listTmuxSessions(tmuxSocketArgs());
162
+ customSessions
163
+ .filter(s => isSecureLinkSession(s.name))
164
+ .forEach(s => sessions.push({ ...s, socketArgs: tmuxSocketArgs(), source: 'custom' }));
165
+
166
+ const legacySessions = await listTmuxSessions();
167
+ legacySessions
168
+ .filter(s => isSecureLinkSession(s.name))
169
+ .forEach(s => sessions.push({ ...s, socketArgs: [], source: 'legacy' }));
170
+
171
+ return sessions;
172
+ }
142
173
  }
143
174
  } catch (err) {
144
175
  spinner.fail(`tmux check/install failed: ${err.message}`);
@@ -811,16 +842,32 @@ async function hostDashboard(uid, password, serviceConfig, tunnelProcess, sessio
811
842
 
812
843
  try {
813
844
  await execaCommand('tmux -V', { reject: true });
814
- const sessionCheck = await execa('tmux', [...tmuxSocketArgs(), 'has-session', '-t', TMUX_SESSION_NAME], { reject: false });
815
- if (sessionCheck.exitCode !== 0) {
845
+ const sessions = await getMirrorableSessions();
846
+ if (sessions.length === 0) {
816
847
  console.log(chalk.yellow(' ⚠️ No mirrored terminal session is active yet.'));
817
848
  console.log(chalk.dim(' A client must choose "Connect via SSH" first. SCP-only clients do not create a tmux session.'));
818
849
  console.log(chalk.dim(' tmux is needed on the host machine only; the client does not need tmux.'));
819
850
  logSessionEvent('host_mirror_missing_session', {}, 'warn');
820
851
  return waitForAction();
821
852
  }
822
- await execa('tmux', [...tmuxSocketArgs(), 'attach', '-t', TMUX_SESSION_NAME, '-r'], { stdio: 'inherit', reject: false });
823
- logSessionEvent('host_mirror_attached');
853
+
854
+ let target = sessions[0];
855
+ if (sessions.length > 1) {
856
+ const { sessionChoice } = await inquirer.prompt([{
857
+ type: 'list',
858
+ name: 'sessionChoice',
859
+ message: 'Select an active client session to mirror:',
860
+ choices: sessions.map((s, idx) => {
861
+ const created = s.createdAt ? new Date(s.createdAt * 1000).toLocaleTimeString() : 'Unknown';
862
+ const label = `${s.name} ${chalk.dim(`(started ${created})`)}`;
863
+ return { name: label, value: String(idx) };
864
+ }),
865
+ }]);
866
+ target = sessions[parseInt(sessionChoice, 10)] || sessions[0];
867
+ }
868
+
869
+ await execa('tmux', [...target.socketArgs, 'attach', '-t', target.name, '-r'], { stdio: 'inherit', reject: false });
870
+ logSessionEvent('host_mirror_attached', { session: target.name, source: target.source });
824
871
  } catch (err) {
825
872
  console.log(chalk.yellow(' ⚠️ Could not attach to tmux.'));
826
873
  console.log(chalk.dim(` ${err.message}`));
@@ -884,7 +931,13 @@ async function hostDashboard(uid, password, serviceConfig, tunnelProcess, sessio
884
931
  await execaCommand('powershell -NoProfile -Command "Get-CimInstance Win32_Process -Filter \\"name = \'sshd.exe\'\\" | Where-Object { $_.CommandLine -match \'sshd:.*@\' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }"', { reject: false });
885
932
  } else {
886
933
  await execaCommand("pkill -f 'sshd:.*@'", { shell: true, reject: false });
887
- await execa('tmux', [...tmuxSocketArgs(), 'kill-session', '-t', TMUX_SESSION_NAME], { reject: false });
934
+ await execa('tmux', [...tmuxSocketArgs(), 'kill-server'], { reject: false });
935
+ const legacySessions = await listTmuxSessions();
936
+ for (const session of legacySessions) {
937
+ if (isSecureLinkSession(session.name)) {
938
+ await execa('tmux', ['kill-session', '-t', session.name], { reject: false });
939
+ }
940
+ }
888
941
  }
889
942
  spinner.succeed('All client SSH sessions terminated');
890
943
  logSessionEvent('host_sessions_terminated');