@balaji003/lantransfer 1.0.7 → 1.0.8
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 +25 -17
- package/server.js +95 -40
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -347,6 +347,8 @@
|
|
|
347
347
|
evtSource = new EventSource('/events');
|
|
348
348
|
evtSource.onmessage = e => {
|
|
349
349
|
state = JSON.parse(e.data);
|
|
350
|
+
console.log('[sse] State update — peers:', state.peers?.length, 'files:', state.sharedFiles?.length,
|
|
351
|
+
'transfers:', state.transfers?.length, 'incoming:', !!state.incomingRequest);
|
|
350
352
|
// Auto-select the latest file if none selected
|
|
351
353
|
if (!selectedFileId && state.sharedFiles && state.sharedFiles.length > 0) {
|
|
352
354
|
selectedFileId = state.sharedFiles[state.sharedFiles.length - 1].id;
|
|
@@ -354,9 +356,11 @@
|
|
|
354
356
|
render();
|
|
355
357
|
};
|
|
356
358
|
evtSource.onopen = () => {
|
|
359
|
+
console.log('[sse] Connected');
|
|
357
360
|
document.getElementById('conn-status').textContent = '';
|
|
358
361
|
};
|
|
359
|
-
evtSource.onerror = () => {
|
|
362
|
+
evtSource.onerror = (e) => {
|
|
363
|
+
console.error('[sse] Error/disconnected', e);
|
|
360
364
|
document.getElementById('conn-status').textContent = 'Reconnecting...';
|
|
361
365
|
};
|
|
362
366
|
}
|
|
@@ -366,10 +370,16 @@
|
|
|
366
370
|
// API
|
|
367
371
|
// ──────────────────────────────────────────────────────────────────────────
|
|
368
372
|
function api(endpoint, data) {
|
|
373
|
+
console.log('[api]', endpoint, data || '');
|
|
369
374
|
return fetch('/api/' + endpoint, {
|
|
370
375
|
method: 'POST',
|
|
371
376
|
headers: { 'Content-Type': 'application/json' },
|
|
372
377
|
body: JSON.stringify(data || {}),
|
|
378
|
+
}).then(r => {
|
|
379
|
+
console.log('[api]', endpoint, 'response:', r.status);
|
|
380
|
+
return r;
|
|
381
|
+
}).catch(err => {
|
|
382
|
+
console.error('[api]', endpoint, 'error:', err);
|
|
373
383
|
});
|
|
374
384
|
}
|
|
375
385
|
|
|
@@ -388,13 +398,9 @@
|
|
|
388
398
|
if (selectedFileId === id) selectedFileId = null;
|
|
389
399
|
}
|
|
390
400
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
document.getElementById('browse-btn').textContent = 'Opening...';
|
|
395
|
-
await api('browse');
|
|
396
|
-
browsing = false;
|
|
397
|
-
document.getElementById('browse-btn').textContent = 'Browse';
|
|
401
|
+
function doBrowse() {
|
|
402
|
+
console.log('[browse] Clicked');
|
|
403
|
+
api('browse');
|
|
398
404
|
}
|
|
399
405
|
|
|
400
406
|
function doSend() {
|
|
@@ -403,12 +409,14 @@
|
|
|
403
409
|
}
|
|
404
410
|
}
|
|
405
411
|
|
|
406
|
-
function respondTransfer(
|
|
407
|
-
|
|
412
|
+
function respondTransfer(accepted) {
|
|
413
|
+
console.log('[respondTransfer] accepted:', accepted);
|
|
414
|
+
api('respond', { accepted: accepted });
|
|
408
415
|
}
|
|
409
416
|
|
|
410
|
-
function openFolder(
|
|
411
|
-
|
|
417
|
+
function openFolder(transferId) {
|
|
418
|
+
console.log('[openFolder] transferId:', transferId);
|
|
419
|
+
api('open-folder', { transferId: transferId });
|
|
412
420
|
}
|
|
413
421
|
|
|
414
422
|
function doShutdown() {
|
|
@@ -531,8 +539,8 @@
|
|
|
531
539
|
// Modal
|
|
532
540
|
const overlay = document.getElementById('modal-overlay');
|
|
533
541
|
const modal = document.getElementById('modal');
|
|
534
|
-
if (state.
|
|
535
|
-
const req = state.
|
|
542
|
+
if (state.incomingRequest) {
|
|
543
|
+
const req = state.incomingRequest;
|
|
536
544
|
overlay.classList.add('show');
|
|
537
545
|
modal.innerHTML =
|
|
538
546
|
'<div class="modal-title">Incoming File</div>'
|
|
@@ -540,8 +548,8 @@
|
|
|
540
548
|
+ '<div class="modal-file">' + esc(req.filename) + '</div>'
|
|
541
549
|
+ '<div class="modal-size">' + formatSize(req.size) + '</div>'
|
|
542
550
|
+ '<div class="modal-buttons">'
|
|
543
|
-
+ '<button class="btn-accept" onclick="respondTransfer(
|
|
544
|
-
+ '<button class="btn-reject" onclick="respondTransfer(
|
|
551
|
+
+ '<button class="btn-accept" onclick="respondTransfer(true)">Accept</button>'
|
|
552
|
+
+ '<button class="btn-reject" onclick="respondTransfer(false)">Reject</button>'
|
|
545
553
|
+ '</div>';
|
|
546
554
|
} else {
|
|
547
555
|
overlay.classList.remove('show');
|
|
@@ -573,7 +581,7 @@
|
|
|
573
581
|
extra = '<div class="transfer-actions">'
|
|
574
582
|
+ '<span class="transfer-size">' + formatSize(t.size) + '</span>';
|
|
575
583
|
if (t.status === 'completed' && t.direction === 'receive' && t.savePath) {
|
|
576
|
-
extra += '<button class="btn-open-folder" onclick="openFolder(\'' + esc(t.
|
|
584
|
+
extra += '<button class="btn-open-folder" onclick="openFolder(\'' + esc(t.id) + '\')">Open Folder</button>';
|
|
577
585
|
}
|
|
578
586
|
extra += '</div>';
|
|
579
587
|
}
|
package/server.js
CHANGED
|
@@ -62,7 +62,7 @@ let downloadDir = config.downloadDir || path.join(os.homedir(), 'Downloads');
|
|
|
62
62
|
const sharedFiles = []; // [ { id, path, name, size } ]
|
|
63
63
|
const peers = new Map(); // id → { device_name, ip, tcp_port, last_seen }
|
|
64
64
|
const transfers = []; // [ TransferEntry ]
|
|
65
|
-
|
|
65
|
+
let pendingRequest = null; // { filename, size, senderName, resolve } or null
|
|
66
66
|
let idCounter = 0;
|
|
67
67
|
let browseInProgress = false;
|
|
68
68
|
|
|
@@ -114,9 +114,9 @@ function serializeState() {
|
|
|
114
114
|
speed: t.speed, error: t.error,
|
|
115
115
|
savePath: t.savePath || null,
|
|
116
116
|
})),
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
incomingRequest: pendingRequest
|
|
118
|
+
? { filename: pendingRequest.filename, size: pendingRequest.size, senderName: pendingRequest.senderName }
|
|
119
|
+
: null,
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -180,6 +180,8 @@ function readExact(socket, n) {
|
|
|
180
180
|
const htmlPath = path.join(__dirname, 'public', 'index.html');
|
|
181
181
|
|
|
182
182
|
const server = http.createServer(async (req, res) => {
|
|
183
|
+
console.log(` [http] ${req.method} ${req.url}`);
|
|
184
|
+
|
|
183
185
|
// ── Serve the web UI ──
|
|
184
186
|
if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {
|
|
185
187
|
fs.readFile(htmlPath, (err, data) => {
|
|
@@ -217,7 +219,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
217
219
|
'Connection': 'keep-alive',
|
|
218
220
|
});
|
|
219
221
|
sseClients.add(res);
|
|
220
|
-
|
|
222
|
+
console.log(` [sse] Client connected (total: ${sseClients.size})`);
|
|
223
|
+
req.on('close', () => {
|
|
224
|
+
sseClients.delete(res);
|
|
225
|
+
console.log(` [sse] Client disconnected (total: ${sseClients.size})`);
|
|
226
|
+
});
|
|
221
227
|
res.write(`data: ${serializeState()}\n\n`);
|
|
222
228
|
return;
|
|
223
229
|
}
|
|
@@ -233,32 +239,40 @@ const server = http.createServer(async (req, res) => {
|
|
|
233
239
|
console.log(` [toggle] Discoverable: ${isDiscoverable}`);
|
|
234
240
|
}
|
|
235
241
|
else if (req.url === '/api/browse') {
|
|
242
|
+
// Respond immediately so we don't block the HTTP connection
|
|
243
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
244
|
+
res.end('ok');
|
|
236
245
|
if (browseInProgress) {
|
|
237
246
|
console.log(' [browse] Dialog already open, ignoring');
|
|
238
247
|
} else {
|
|
239
248
|
browseInProgress = true;
|
|
240
249
|
console.log(' [browse] Opening file dialog...');
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
250
|
+
try {
|
|
251
|
+
const fp = await openFileDialog();
|
|
252
|
+
browseInProgress = false;
|
|
253
|
+
if (fp) {
|
|
245
254
|
const s = fs.statSync(fp);
|
|
246
255
|
const file = { id: String(idCounter++), path: fp, name: path.basename(fp), size: s.size };
|
|
247
256
|
sharedFiles.push(file);
|
|
248
257
|
console.log(` [browse] Added: ${file.name} (${formatSize(file.size)})`);
|
|
249
|
-
}
|
|
250
|
-
console.
|
|
258
|
+
} else {
|
|
259
|
+
console.log(' [browse] Cancelled by user');
|
|
251
260
|
}
|
|
252
|
-
}
|
|
253
|
-
|
|
261
|
+
} catch (e) {
|
|
262
|
+
browseInProgress = false;
|
|
263
|
+
console.error(` [browse] Error: ${e.message}`);
|
|
254
264
|
}
|
|
265
|
+
broadcast();
|
|
255
266
|
}
|
|
267
|
+
return; // already responded
|
|
256
268
|
}
|
|
257
269
|
else if (req.url === '/api/remove-file') {
|
|
258
270
|
const idx = sharedFiles.findIndex(f => f.id === data.fileId);
|
|
259
271
|
if (idx !== -1) {
|
|
260
272
|
console.log(` [files] Removed: ${sharedFiles[idx].name}`);
|
|
261
273
|
sharedFiles.splice(idx, 1);
|
|
274
|
+
} else {
|
|
275
|
+
console.log(` [files] Remove failed — fileId not found: ${data.fileId}`);
|
|
262
276
|
}
|
|
263
277
|
}
|
|
264
278
|
else if (req.url === '/api/send') {
|
|
@@ -266,9 +280,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
266
280
|
const file = sharedFiles.find(f => f.id === data.fileId);
|
|
267
281
|
if (peer && file) {
|
|
268
282
|
console.log(` [send] ${file.name} -> ${peer.device_name} (${peer.ip})`);
|
|
269
|
-
sendFile(peer, { ...file });
|
|
283
|
+
sendFile(peer, { ...file }).catch(e => console.error(` [send] Unhandled: ${e.message}`));
|
|
270
284
|
} else {
|
|
271
|
-
console.log(` [send] Failed — peer: ${!!peer}, file: ${!!file}`);
|
|
285
|
+
console.log(` [send] Failed — peer: ${!!peer} (${data.peerId}), file: ${!!file} (${data.fileId})`);
|
|
272
286
|
}
|
|
273
287
|
}
|
|
274
288
|
else if (req.url === '/api/rename') {
|
|
@@ -278,34 +292,62 @@ const server = http.createServer(async (req, res) => {
|
|
|
278
292
|
config.deviceName = newName;
|
|
279
293
|
saveConfig(config);
|
|
280
294
|
console.log(` [rename] Device name set to: ${deviceName}`);
|
|
295
|
+
} else {
|
|
296
|
+
console.log(` [rename] Invalid name: "${data.name}"`);
|
|
281
297
|
}
|
|
282
298
|
}
|
|
283
299
|
else if (req.url === '/api/set-download-dir') {
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
300
|
+
// Respond immediately so we don't block the HTTP connection
|
|
301
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
302
|
+
res.end('ok');
|
|
303
|
+
console.log(' [config] Opening folder dialog...');
|
|
304
|
+
try {
|
|
305
|
+
const dir = await openFolderDialog();
|
|
306
|
+
if (dir) {
|
|
307
|
+
downloadDir = dir;
|
|
308
|
+
config.downloadDir = dir;
|
|
309
|
+
saveConfig(config);
|
|
310
|
+
console.log(` [config] Download dir set to: ${downloadDir}`);
|
|
311
|
+
} else {
|
|
312
|
+
console.log(' [config] Folder dialog cancelled');
|
|
313
|
+
}
|
|
314
|
+
} catch (e) {
|
|
315
|
+
console.error(` [config] Folder dialog error: ${e.message}`);
|
|
291
316
|
}
|
|
317
|
+
broadcast();
|
|
318
|
+
return; // already responded
|
|
292
319
|
}
|
|
293
320
|
else if (req.url === '/api/respond') {
|
|
294
|
-
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
321
|
+
console.log(` [respond] Received: accepted=${data.accepted}`);
|
|
322
|
+
if (pendingRequest) {
|
|
323
|
+
pendingRequest.resolve(!!data.accepted);
|
|
324
|
+
console.log(` [respond] ${data.accepted ? 'Accepted' : 'Rejected'}: ${pendingRequest.filename}`);
|
|
325
|
+
pendingRequest = null;
|
|
326
|
+
} else {
|
|
327
|
+
console.log(` [respond] WARNING: No pending request`);
|
|
299
328
|
}
|
|
300
329
|
}
|
|
301
330
|
else if (req.url === '/api/open-folder') {
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
331
|
+
const transferId = data.transferId;
|
|
332
|
+
const t = transfers.find(tr => tr.id === transferId);
|
|
333
|
+
if (t && t.savePath) {
|
|
334
|
+
console.log(` [open] Opening folder for: ${t.savePath}`);
|
|
335
|
+
if (fs.existsSync(t.savePath)) {
|
|
336
|
+
if (process.platform === 'win32') exec(`explorer /select,"${t.savePath}"`);
|
|
337
|
+
else if (process.platform === 'darwin') exec(`open -R "${t.savePath}"`);
|
|
338
|
+
else exec(`xdg-open "${path.dirname(t.savePath)}"`);
|
|
339
|
+
} else {
|
|
340
|
+
console.log(` [open] File no longer exists: ${t.savePath}`);
|
|
341
|
+
// Open the directory instead
|
|
342
|
+
const dir = path.dirname(t.savePath);
|
|
343
|
+
if (fs.existsSync(dir)) {
|
|
344
|
+
if (process.platform === 'win32') exec(`explorer "${dir}"`);
|
|
345
|
+
else if (process.platform === 'darwin') exec(`open "${dir}"`);
|
|
346
|
+
else exec(`xdg-open "${dir}"`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
console.log(` [open] Failed — transferId: ${transferId}, found: ${!!t}, savePath: ${t ? t.savePath : 'N/A'}`);
|
|
309
351
|
}
|
|
310
352
|
}
|
|
311
353
|
else if (req.url === '/api/shutdown') {
|
|
@@ -315,6 +357,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
315
357
|
setTimeout(() => process.exit(0), 200);
|
|
316
358
|
return;
|
|
317
359
|
}
|
|
360
|
+
else {
|
|
361
|
+
console.log(` [http] Unknown POST endpoint: ${req.url}`);
|
|
362
|
+
}
|
|
318
363
|
|
|
319
364
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
320
365
|
res.end('ok');
|
|
@@ -428,33 +473,41 @@ tcpServer.on('error', err => {
|
|
|
428
473
|
tcpServer.listen(TRANSFER_PORT);
|
|
429
474
|
|
|
430
475
|
async function handleIncoming(socket) {
|
|
476
|
+
const remoteAddr = `${socket.remoteAddress}:${socket.remotePort}`;
|
|
477
|
+
console.log(` [recv] TCP connection from ${remoteAddr}`);
|
|
478
|
+
|
|
431
479
|
// Read header length (8 bytes, little-endian u64)
|
|
432
480
|
const lenBuf = await readExact(socket, 8);
|
|
433
481
|
const headerLen = Number(lenBuf.readBigUInt64LE(0));
|
|
482
|
+
console.log(` [recv] Header length: ${headerLen} bytes`);
|
|
434
483
|
|
|
435
484
|
// Read header JSON
|
|
436
485
|
const headerBuf = await readExact(socket, headerLen);
|
|
437
486
|
const header = JSON.parse(headerBuf.toString('utf-8'));
|
|
438
487
|
const { filename, size, sender_name } = header;
|
|
439
488
|
|
|
440
|
-
console.log(` Incoming: ${filename} (${formatSize(size)}) from ${sender_name}`);
|
|
489
|
+
console.log(` [recv] Incoming: "${filename}" (${formatSize(size)}) from ${sender_name}`);
|
|
441
490
|
|
|
442
491
|
// Prompt user for accept / reject
|
|
443
|
-
|
|
492
|
+
console.log(` [recv] Waiting for user decision...`);
|
|
493
|
+
|
|
444
494
|
const accepted = await new Promise(resolve => {
|
|
445
|
-
|
|
495
|
+
pendingRequest = { filename, size, senderName: sender_name, resolve };
|
|
446
496
|
broadcast();
|
|
447
497
|
|
|
448
498
|
// Auto-reject if sender disconnects while waiting
|
|
449
499
|
socket.once('close', () => {
|
|
450
|
-
if (
|
|
451
|
-
|
|
500
|
+
if (pendingRequest && pendingRequest.resolve === resolve) {
|
|
501
|
+
console.log(` [recv] Sender disconnected while waiting, auto-rejecting`);
|
|
502
|
+
pendingRequest = null;
|
|
452
503
|
resolve(false);
|
|
453
504
|
broadcast();
|
|
454
505
|
}
|
|
455
506
|
});
|
|
456
507
|
});
|
|
457
508
|
|
|
509
|
+
console.log(` [recv] User decision: ${accepted ? 'ACCEPTED' : 'REJECTED'}`);
|
|
510
|
+
|
|
458
511
|
// Send decision byte
|
|
459
512
|
socket.write(Buffer.from([accepted ? 1 : 0]));
|
|
460
513
|
if (!accepted) { socket.end(); return; }
|
|
@@ -481,6 +534,7 @@ async function handleIncoming(socket) {
|
|
|
481
534
|
}
|
|
482
535
|
|
|
483
536
|
t.savePath = savePath;
|
|
537
|
+
console.log(` [recv] Saving to: ${savePath}`);
|
|
484
538
|
|
|
485
539
|
const ws = fs.createWriteStream(savePath);
|
|
486
540
|
let received = 0;
|
|
@@ -508,7 +562,7 @@ async function handleIncoming(socket) {
|
|
|
508
562
|
t.status = 'completed';
|
|
509
563
|
t.progress = 100;
|
|
510
564
|
broadcast();
|
|
511
|
-
console.log(`
|
|
565
|
+
console.log(` [recv] Complete: ${savePath} (${formatSize(received)})`);
|
|
512
566
|
resolve();
|
|
513
567
|
});
|
|
514
568
|
socket.on('error', err => {
|
|
@@ -516,6 +570,7 @@ async function handleIncoming(socket) {
|
|
|
516
570
|
t.status = 'failed';
|
|
517
571
|
t.error = err.message;
|
|
518
572
|
broadcast();
|
|
573
|
+
console.error(` [recv] Socket error: ${err.message}`);
|
|
519
574
|
reject(err);
|
|
520
575
|
});
|
|
521
576
|
});
|