@balaji003/lantransfer 1.0.6 → 1.0.7

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": "@balaji003/lantransfer",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "LAN File Transfer — peer-to-peer file sharing over local network. Zero dependencies.",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/index.html CHANGED
@@ -158,6 +158,34 @@
158
158
  }
159
159
  .file-item-remove:hover { color: var(--red); }
160
160
 
161
+ .transfer-actions {
162
+ display: flex; align-items: center; gap: 10px;
163
+ margin-top: 8px; font-size: 12px;
164
+ }
165
+ .transfer-size { color: var(--text-dim); }
166
+ .btn-open-folder {
167
+ background: none; border: 1px solid var(--border); color: var(--accent);
168
+ padding: 3px 10px; border-radius: 4px; cursor: pointer;
169
+ font-size: 12px; transition: all 0.12s;
170
+ }
171
+ .btn-open-folder:hover { background: var(--surface-hover); border-color: var(--accent); }
172
+
173
+ .download-dir {
174
+ display: flex; align-items: center; gap: 8px; padding: 8px 24px;
175
+ background: var(--surface); border-bottom: 1px solid var(--border);
176
+ font-size: 12px; color: var(--text-dim); flex-shrink: 0;
177
+ }
178
+ .download-dir-path {
179
+ color: var(--text); font-weight: 500; overflow: hidden;
180
+ text-overflow: ellipsis; white-space: nowrap; max-width: 400px;
181
+ }
182
+ .btn-change-dir {
183
+ background: none; border: 1px solid var(--border); color: var(--accent);
184
+ padding: 2px 8px; border-radius: 4px; cursor: pointer;
185
+ font-size: 11px; transition: all 0.12s; white-space: nowrap;
186
+ }
187
+ .btn-change-dir:hover { background: var(--surface-hover); border-color: var(--accent); }
188
+
161
189
  /* ── Transfers ── */
162
190
  .transfers-section { flex: 1; overflow-y: auto; padding: 16px 24px; }
163
191
  .transfer {
@@ -256,6 +284,13 @@
256
284
  </div>
257
285
  </div>
258
286
 
287
+ <!-- Download location -->
288
+ <div class="download-dir">
289
+ <span>Save to:</span>
290
+ <span class="download-dir-path" id="download-dir" title=""></span>
291
+ <button class="btn-change-dir" onclick="api('set-download-dir')">Change</button>
292
+ </div>
293
+
259
294
  <!-- Main -->
260
295
  <div class="main">
261
296
  <!-- Sidebar: peers -->
@@ -372,6 +407,10 @@
372
407
  api('respond', { requestId: requestId, accepted: accepted });
373
408
  }
374
409
 
410
+ function openFolder(filePath) {
411
+ api('open-folder', { path: filePath });
412
+ }
413
+
375
414
  function doShutdown() {
376
415
  if (confirm('Stop the server? This will close LAN Transfer.')) {
377
416
  api('shutdown');
@@ -403,6 +442,11 @@
403
442
  }
404
443
  document.getElementById('local-ip').textContent = state.localIP || '';
405
444
 
445
+ // Download dir
446
+ const dirEl = document.getElementById('download-dir');
447
+ dirEl.textContent = state.downloadDir || '';
448
+ dirEl.title = state.downloadDir || '';
449
+
406
450
  // Toggle
407
451
  const toggle = document.getElementById('toggle');
408
452
  const toggleText = document.getElementById('toggle-text');
@@ -525,6 +569,13 @@
525
569
  + '<span>' + formatSize(t.size) + '</span>'
526
570
  + '<span>' + formatSpeed(t.speed) + '</span>'
527
571
  + '</div>';
572
+ } else if (t.status === 'completed' || t.status === 'failed') {
573
+ extra = '<div class="transfer-actions">'
574
+ + '<span class="transfer-size">' + formatSize(t.size) + '</span>';
575
+ if (t.status === 'completed' && t.direction === 'receive' && t.savePath) {
576
+ extra += '<button class="btn-open-folder" onclick="openFolder(\'' + esc(t.savePath.replace(/\\/g, '\\\\')) + '\')">Open Folder</button>';
577
+ }
578
+ extra += '</div>';
528
579
  }
529
580
 
530
581
  return '<div class="transfer">'
package/server.js CHANGED
@@ -58,6 +58,7 @@ const config = loadConfig();
58
58
  // ──────────────────────────────────────────────────────────────────────────────
59
59
  let isDiscoverable = true;
60
60
  let deviceName = config.deviceName || os.hostname();
61
+ let downloadDir = config.downloadDir || path.join(os.homedir(), 'Downloads');
61
62
  const sharedFiles = []; // [ { id, path, name, size } ]
62
63
  const peers = new Map(); // id → { device_name, ip, tcp_port, last_seen }
63
64
  const transfers = []; // [ TransferEntry ]
@@ -99,6 +100,7 @@ function serializeState() {
99
100
  return JSON.stringify({
100
101
  isDiscoverable,
101
102
  deviceName,
103
+ downloadDir,
102
104
  localIP: getLocalIP(),
103
105
  peers: [...peers.entries()].map(([id, p]) => ({
104
106
  id, name: p.device_name, ip: p.ip,
@@ -110,6 +112,7 @@ function serializeState() {
110
112
  direction: t.direction, peerName: t.peerName,
111
113
  status: t.status, progress: t.progress,
112
114
  speed: t.speed, error: t.error,
115
+ savePath: t.savePath || null,
113
116
  })),
114
117
  incomingRequests: [...pendingRequests.entries()].map(([id, r]) => ({
115
118
  id, filename: r.filename, size: r.size, senderName: r.senderName,
@@ -277,6 +280,16 @@ const server = http.createServer(async (req, res) => {
277
280
  console.log(` [rename] Device name set to: ${deviceName}`);
278
281
  }
279
282
  }
283
+ else if (req.url === '/api/set-download-dir') {
284
+ // Open native folder picker
285
+ const dir = await openFolderDialog();
286
+ if (dir) {
287
+ downloadDir = dir;
288
+ config.downloadDir = dir;
289
+ saveConfig(config);
290
+ console.log(` [config] Download dir set to: ${downloadDir}`);
291
+ }
292
+ }
280
293
  else if (req.url === '/api/respond') {
281
294
  const pending = pendingRequests.get(data.requestId);
282
295
  if (pending) {
@@ -285,6 +298,16 @@ const server = http.createServer(async (req, res) => {
285
298
  console.log(` [respond] ${data.accepted ? 'Accepted' : 'Rejected'} transfer ${data.requestId}`);
286
299
  }
287
300
  }
301
+ else if (req.url === '/api/open-folder') {
302
+ const filePath = data.path;
303
+ if (filePath && fs.existsSync(filePath)) {
304
+ const dir = path.dirname(filePath);
305
+ if (process.platform === 'win32') exec(`explorer /select,"${filePath}"`);
306
+ else if (process.platform === 'darwin') exec(`open -R "${filePath}"`);
307
+ else exec(`xdg-open "${dir}"`);
308
+ console.log(` [open] ${filePath}`);
309
+ }
310
+ }
288
311
  else if (req.url === '/api/shutdown') {
289
312
  res.writeHead(200, { 'Content-Type': 'text/plain' });
290
313
  res.end('ok');
@@ -322,6 +345,22 @@ function openFileDialog() {
322
345
  });
323
346
  }
324
347
 
348
+ function openFolderDialog() {
349
+ return new Promise(resolve => {
350
+ let cmd;
351
+ if (process.platform === 'win32') {
352
+ cmd = 'powershell -sta -command "Add-Type -AssemblyName System.Windows.Forms; $f = New-Object System.Windows.Forms.FolderBrowserDialog; $f.Description = \'Select download folder\'; if($f.ShowDialog() -eq \'OK\'){Write-Output $f.SelectedPath}"';
353
+ } else if (process.platform === 'darwin') {
354
+ cmd = 'osascript -e \'POSIX path of (choose folder with prompt "Select download folder")\'';
355
+ } else {
356
+ cmd = 'zenity --file-selection --directory --title="Select download folder" 2>/dev/null || kdialog --getexistingdirectory . 2>/dev/null';
357
+ }
358
+ exec(cmd, { encoding: 'utf-8', windowsHide: true, timeout: 120_000 }, (err, stdout) => {
359
+ resolve(err ? null : (stdout.trim() || null));
360
+ });
361
+ });
362
+ }
363
+
325
364
  // ──────────────────────────────────────────────────────────────────────────────
326
365
  // UDP discovery
327
366
  // ──────────────────────────────────────────────────────────────────────────────
@@ -430,8 +469,8 @@ async function handleIncoming(socket) {
430
469
  transfers.push(t);
431
470
  broadcast();
432
471
 
433
- // Unique save path in Downloads
434
- const dl = path.join(os.homedir(), 'Downloads');
472
+ // Unique save path in download directory
473
+ const dl = downloadDir;
435
474
  if (!fs.existsSync(dl)) fs.mkdirSync(dl, { recursive: true });
436
475
  let savePath = path.join(dl, filename);
437
476
  let counter = 1;
@@ -441,6 +480,8 @@ async function handleIncoming(socket) {
441
480
  savePath = path.join(dl, `${base} (${counter++})${ext}`);
442
481
  }
443
482
 
483
+ t.savePath = savePath;
484
+
444
485
  const ws = fs.createWriteStream(savePath);
445
486
  let received = 0;
446
487
  let lastBc = 0;