termbeam 1.0.5 → 1.0.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": "termbeam",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Beam your terminal to any device — mobile-optimized web terminal with multi-session support",
5
5
  "main": "src/server.js",
6
6
  "bin": {
package/src/routes.js CHANGED
@@ -45,9 +45,9 @@ function setupRoutes(app, { auth, sessions, config }) {
45
45
  });
46
46
 
47
47
  // Pages
48
- app.get('/', auth.middleware, (_req, res) => res.sendFile(path.join(PUBLIC_DIR, 'index.html')));
48
+ app.get('/', auth.middleware, (_req, res) => res.sendFile('index.html', { root: PUBLIC_DIR }));
49
49
  app.get('/terminal', auth.middleware, (_req, res) =>
50
- res.sendFile(path.join(PUBLIC_DIR, 'terminal.html')),
50
+ res.sendFile('terminal.html', { root: PUBLIC_DIR }),
51
51
  );
52
52
 
53
53
  // Session API
@@ -61,7 +61,7 @@ function setupRoutes(app, { auth, sessions, config }) {
61
61
  // Validate shell field
62
62
  if (shell) {
63
63
  const availableShells = detectShells();
64
- const isValid = availableShells.some(s => s.path === shell || s.cmd === shell);
64
+ const isValid = availableShells.some((s) => s.path === shell || s.cmd === shell);
65
65
  if (!isValid) {
66
66
  return res.status(400).json({ error: 'Invalid shell' });
67
67
  }
@@ -150,13 +150,14 @@ function setupRoutes(app, { auth, sessions, config }) {
150
150
  if (!buffer.length) {
151
151
  return res.status(400).json({ error: 'No image data' });
152
152
  }
153
- const ext = {
154
- 'image/png': '.png',
155
- 'image/jpeg': '.jpg',
156
- 'image/gif': '.gif',
157
- 'image/webp': '.webp',
158
- 'image/bmp': '.bmp',
159
- }[contentType] || '.png';
153
+ const ext =
154
+ {
155
+ 'image/png': '.png',
156
+ 'image/jpeg': '.jpg',
157
+ 'image/gif': '.gif',
158
+ 'image/webp': '.webp',
159
+ 'image/bmp': '.bmp',
160
+ }[contentType] || '.png';
160
161
  const filename = `termbeam-${crypto.randomUUID()}${ext}`;
161
162
  const filepath = path.join(os.tmpdir(), filename);
162
163
  fs.writeFileSync(filepath, buffer);
@@ -173,7 +174,7 @@ function setupRoutes(app, { auth, sessions, config }) {
173
174
 
174
175
  // Directory listing for folder browser
175
176
  app.get('/api/dirs', auth.middleware, (req, res) => {
176
- const query = req.query.q || (config.cwd + path.sep);
177
+ const query = req.query.q || config.cwd + path.sep;
177
178
  const endsWithSep = query.endsWith('/') || query.endsWith('\\');
178
179
  const dir = endsWithSep ? query : path.dirname(query);
179
180
  const prefix = endsWithSep ? '' : path.basename(query);
package/src/tunnel.js CHANGED
@@ -47,7 +47,10 @@ function loadPersistedTunnel() {
47
47
 
48
48
  function savePersistedTunnel(id) {
49
49
  fs.mkdirSync(TUNNEL_CONFIG_DIR, { recursive: true });
50
- fs.writeFileSync(TUNNEL_CONFIG_PATH, JSON.stringify({ tunnelId: id, createdAt: new Date().toISOString() }, null, 2));
50
+ fs.writeFileSync(
51
+ TUNNEL_CONFIG_PATH,
52
+ JSON.stringify({ tunnelId: id, createdAt: new Date().toISOString() }, null, 2),
53
+ );
51
54
  }
52
55
 
53
56
  function deletePersisted() {
@@ -68,7 +71,10 @@ function deletePersisted() {
68
71
  function isTunnelValid(id) {
69
72
  try {
70
73
  if (!SAFE_ID_RE.test(id)) return false;
71
- execFileSync(devtunnelCmd, ['show', id, '--json'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
74
+ execFileSync(devtunnelCmd, ['show', id, '--json'], {
75
+ encoding: 'utf-8',
76
+ stdio: ['pipe', 'pipe', 'pipe'],
77
+ });
72
78
  return true;
73
79
  } catch {
74
80
  return false;
@@ -87,7 +93,9 @@ async function startTunnel(port, options = {}) {
87
93
  log.error('');
88
94
  log.error(' Install it:');
89
95
  log.error(' Windows: winget install Microsoft.devtunnel');
90
- log.error(' or: Invoke-WebRequest -Uri https://aka.ms/TunnelsCliDownload/win-x64 -OutFile devtunnel.exe');
96
+ log.error(
97
+ ' or: Invoke-WebRequest -Uri https://aka.ms/TunnelsCliDownload/win-x64 -OutFile devtunnel.exe',
98
+ );
91
99
  log.error(' macOS: brew install --cask devtunnel');
92
100
  log.error(' Linux: curl -sL https://aka.ms/DevTunnelCliInstall | bash');
93
101
  log.error('');
@@ -103,7 +111,10 @@ async function startTunnel(port, options = {}) {
103
111
  // Ensure user is logged in
104
112
  let loggedIn = false;
105
113
  try {
106
- const userOut = execFileSync(devtunnelCmd, ['user', 'show'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
114
+ const userOut = execFileSync(devtunnelCmd, ['user', 'show'], {
115
+ encoding: 'utf-8',
116
+ stdio: ['pipe', 'pipe', 'pipe'],
117
+ });
107
118
  // user show can succeed but show "not logged in" status
108
119
  loggedIn = userOut && !userOut.toLowerCase().includes('not logged in');
109
120
  } catch {}
@@ -111,7 +122,18 @@ async function startTunnel(port, options = {}) {
111
122
  if (!loggedIn) {
112
123
  log.info('devtunnel not logged in, launching login...');
113
124
  log.info('A browser window will open for authentication.');
114
- execFileSync(devtunnelCmd, ['user', 'login'], { stdio: 'inherit' });
125
+ try {
126
+ execFileSync(devtunnelCmd, ['user', 'login'], { stdio: 'inherit' });
127
+ } catch (loginErr) {
128
+ log.error('');
129
+ log.error(' DevTunnel login failed. To use tunnels, run:');
130
+ log.error(' devtunnel user login');
131
+ log.error('');
132
+ log.error(' Or start without a tunnel:');
133
+ log.error(' termbeam --no-tunnel');
134
+ log.error('');
135
+ return null;
136
+ }
115
137
  }
116
138
 
117
139
  const persisted = options.persisted;
@@ -130,7 +152,9 @@ async function startTunnel(port, options = {}) {
130
152
  if (saved) {
131
153
  log.info('Persisted tunnel expired, creating new one');
132
154
  }
133
- const createOut = execFileSync(devtunnelCmd, ['create', '--expiration', '30d', '--json'], { encoding: 'utf-8' });
155
+ const createOut = execFileSync(devtunnelCmd, ['create', '--expiration', '30d', '--json'], {
156
+ encoding: 'utf-8',
157
+ });
134
158
  const tunnelData = JSON.parse(createOut);
135
159
  tunnelId = tunnelData.tunnel.tunnelId;
136
160
  savePersistedTunnel(tunnelId);
@@ -140,7 +164,9 @@ async function startTunnel(port, options = {}) {
140
164
  tunnelMode = 'ephemeral';
141
165
  tunnelExpiry = '1 day';
142
166
  // Ephemeral tunnel — create fresh, will be deleted on shutdown
143
- const createOut = execFileSync(devtunnelCmd, ['create', '--expiration', '1d', '--json'], { encoding: 'utf-8' });
167
+ const createOut = execFileSync(devtunnelCmd, ['create', '--expiration', '1d', '--json'], {
168
+ encoding: 'utf-8',
169
+ });
144
170
  const tunnelData = JSON.parse(createOut);
145
171
  tunnelId = tunnelData.tunnel.tunnelId;
146
172
  log.info(`Created ephemeral tunnel ${tunnelId}`);
@@ -148,10 +174,18 @@ async function startTunnel(port, options = {}) {
148
174
 
149
175
  // Idempotent port and access setup
150
176
  try {
151
- execFileSync(devtunnelCmd, ['port', 'create', tunnelId, '-p', String(port), '--protocol', 'http'], { stdio: 'pipe' });
177
+ execFileSync(
178
+ devtunnelCmd,
179
+ ['port', 'create', tunnelId, '-p', String(port), '--protocol', 'http'],
180
+ { stdio: 'pipe' },
181
+ );
152
182
  } catch {}
153
183
  try {
154
- execFileSync(devtunnelCmd, ['access', 'create', tunnelId, '-p', String(port), '--anonymous'], { stdio: 'pipe' });
184
+ execFileSync(
185
+ devtunnelCmd,
186
+ ['access', 'create', tunnelId, '-p', String(port), '--anonymous'],
187
+ { stdio: 'pipe' },
188
+ );
155
189
  } catch {}
156
190
 
157
191
  const hostProc = spawn(devtunnelCmd, ['host', tunnelId], {
@@ -193,8 +227,13 @@ function cleanupTunnel() {
193
227
  // On Windows, kill the process tree to ensure all children die
194
228
  if (process.platform === 'win32' && tunnelProc.pid) {
195
229
  try {
196
- execFileSync('taskkill', ['/pid', String(tunnelProc.pid), '/T', '/F'], { stdio: 'pipe', timeout: 5000 });
197
- } catch { /* best effort */ }
230
+ execFileSync('taskkill', ['/pid', String(tunnelProc.pid), '/T', '/F'], {
231
+ stdio: 'pipe',
232
+ timeout: 5000,
233
+ });
234
+ } catch {
235
+ /* best effort */
236
+ }
198
237
  } else {
199
238
  tunnelProc.kill('SIGKILL');
200
239
  }