@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 +1 -1
- package/public/index.html +51 -0
- package/server.js +43 -2
package/package.json
CHANGED
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
|
|
434
|
-
const dl =
|
|
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;
|