@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balaji003/lantransfer",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
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
@@ -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
- async function doBrowse() {
392
- if (browsing) return;
393
- browsing = true;
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(requestId, accepted) {
407
- api('respond', { requestId: requestId, accepted: accepted });
412
+ function respondTransfer(accepted) {
413
+ console.log('[respondTransfer] accepted:', accepted);
414
+ api('respond', { accepted: accepted });
408
415
  }
409
416
 
410
- function openFolder(filePath) {
411
- api('open-folder', { path: filePath });
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.incomingRequests && state.incomingRequests.length > 0) {
535
- const req = state.incomingRequests[0];
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(\'' + esc(req.id) + '\', true)">Accept</button>'
544
- + '<button class="btn-reject" onclick="respondTransfer(\'' + esc(req.id) + '\', false)">Reject</button>'
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.savePath.replace(/\\/g, '\\\\')) + '\')">Open Folder</button>';
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
- const pendingRequests = new Map(); // id → { filename, size, senderName, resolve }
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
- incomingRequests: [...pendingRequests.entries()].map(([id, r]) => ({
118
- id, filename: r.filename, size: r.size, senderName: r.senderName,
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
- req.on('close', () => sseClients.delete(res));
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
- const fp = await openFileDialog();
242
- browseInProgress = false;
243
- if (fp) {
244
- try {
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
- } catch (e) {
250
- console.error(` [browse] Error reading file: ${e.message}`);
258
+ } else {
259
+ console.log(' [browse] Cancelled by user');
251
260
  }
252
- } else {
253
- console.log(' [browse] Cancelled');
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
- // 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}`);
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
- const pending = pendingRequests.get(data.requestId);
295
- if (pending) {
296
- pending.resolve(!!data.accepted);
297
- pendingRequests.delete(data.requestId);
298
- console.log(` [respond] ${data.accepted ? 'Accepted' : 'Rejected'} transfer ${data.requestId}`);
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 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}`);
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
- const reqId = String(idCounter++);
492
+ console.log(` [recv] Waiting for user decision...`);
493
+
444
494
  const accepted = await new Promise(resolve => {
445
- pendingRequests.set(reqId, { filename, size, senderName: sender_name, resolve });
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 (pendingRequests.has(reqId)) {
451
- pendingRequests.delete(reqId);
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(` Saved: ${savePath}`);
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
  });