@balaji003/lantransfer 1.0.2 → 1.0.3

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/bin/cli.js CHANGED
@@ -1,3 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
- import '../server.js';
1
+ #!/usr/bin/env node
2
+
3
+ require('../server.js');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balaji003/lantransfer",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
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
@@ -65,6 +65,17 @@
65
65
  }
66
66
  .toggle.active .toggle-dot { background: var(--green); }
67
67
 
68
+ .header-right { display: flex; align-items: center; gap: 10px; }
69
+
70
+ .btn-shutdown {
71
+ display: flex; align-items: center; gap: 6px;
72
+ padding: 6px 16px; border-radius: 20px; border: 1px solid transparent;
73
+ background: rgba(248, 113, 113, 0.12); color: var(--red);
74
+ font-size: 13px; font-weight: 500; cursor: pointer;
75
+ transition: all 0.2s; user-select: none;
76
+ }
77
+ .btn-shutdown:hover { border-color: var(--red); background: rgba(248, 113, 113, 0.22); }
78
+
68
79
  /* ── Main layout ── */
69
80
  .main { flex: 1; display: flex; overflow: hidden; }
70
81
 
@@ -207,9 +218,14 @@
207
218
  <div class="logo">&#8652; LAN Transfer</div>
208
219
  <div class="device-info" id="device-info"></div>
209
220
  </div>
210
- <div class="toggle" id="toggle" onclick="api('toggle')">
211
- <div class="toggle-dot"></div>
212
- <span id="toggle-text">Hidden</span>
221
+ <div class="header-right">
222
+ <div class="toggle" id="toggle" onclick="api('toggle')">
223
+ <div class="toggle-dot"></div>
224
+ <span id="toggle-text">Hidden</span>
225
+ </div>
226
+ <button class="btn-shutdown" onclick="doShutdown()" title="Stop the server">
227
+ &#x23FB; Stop Server
228
+ </button>
213
229
  </div>
214
230
  </div>
215
231
 
@@ -303,6 +319,13 @@
303
319
  api('respond', { requestId: requestId, accepted: accepted });
304
320
  }
305
321
 
322
+ function doShutdown() {
323
+ if (confirm('Stop the server? This will close LAN Transfer.')) {
324
+ api('shutdown');
325
+ document.body.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100vh;color:#6b6b8d;font-size:18px;">Server stopped. You can close this tab.</div>';
326
+ }
327
+ }
328
+
306
329
  // ──────────────────────────────────────────────────────────────────────────
307
330
  // Render
308
331
  // ──────────────────────────────────────────────────────────────────────────
package/server.js CHANGED
@@ -56,6 +56,22 @@ function getLocalIP() {
56
56
  return '127.0.0.1';
57
57
  }
58
58
 
59
+ /** Compute subnet broadcast address from local IP and netmask. */
60
+ function getBroadcastAddresses() {
61
+ const addrs = [];
62
+ for (const ifaces of Object.values(os.networkInterfaces())) {
63
+ for (const iface of ifaces) {
64
+ if (iface.family === 'IPv4' && !iface.internal && iface.netmask) {
65
+ const ipParts = iface.address.split('.').map(Number);
66
+ const maskParts = iface.netmask.split('.').map(Number);
67
+ const broadcast = ipParts.map((octet, i) => (octet | (~maskParts[i] & 255))).join('.');
68
+ addrs.push(broadcast);
69
+ }
70
+ }
71
+ }
72
+ return addrs.length > 0 ? addrs : ['255.255.255.255'];
73
+ }
74
+
59
75
  function serializeState() {
60
76
  return JSON.stringify({
61
77
  isDiscoverable,
@@ -193,6 +209,13 @@ const server = http.createServer(async (req, res) => {
193
209
  pendingRequests.delete(data.requestId);
194
210
  }
195
211
  }
212
+ else if (req.url === '/api/shutdown') {
213
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
214
+ res.end('ok');
215
+ console.log('\n Server stopped by user.');
216
+ setTimeout(() => process.exit(0), 200);
217
+ return;
218
+ }
196
219
 
197
220
  res.writeHead(200, { 'Content-Type': 'text/plain' });
198
221
  res.end('ok');
@@ -256,11 +279,13 @@ udp.on('error', err => {
256
279
 
257
280
  udp.bind(DISCOVERY_PORT);
258
281
 
259
- // Broadcaster — send beacon every 3 s
282
+ // Broadcaster — send beacon every 3 s (to subnet broadcast addresses)
260
283
  setInterval(() => {
261
284
  if (!isDiscoverable) return;
262
285
  const msg = JSON.stringify({ device_name: deviceName, tcp_port: TRANSFER_PORT });
263
- udp.send(msg, DISCOVERY_PORT, '255.255.255.255', () => {});
286
+ for (const addr of getBroadcastAddresses()) {
287
+ udp.send(msg, DISCOVERY_PORT, addr, () => {});
288
+ }
264
289
  }, BROADCAST_INTERVAL);
265
290
 
266
291
  // Prune stale peers
@@ -478,6 +503,55 @@ function formatSize(bytes) {
478
503
  return (bytes / 1073741824).toFixed(1) + ' GB';
479
504
  }
480
505
 
506
+ // ──────────────────────────────────────────────────────────────────────────────
507
+ // Windows Firewall check
508
+ // ──────────────────────────────────────────────────────────────────────────────
509
+ function checkWindowsFirewall() {
510
+ if (process.platform !== 'win32') return;
511
+
512
+ const ruleName = 'LAN Transfer (lantransfer)';
513
+ const nodeExe = process.execPath;
514
+
515
+ // Check if a firewall rule already exists for this app
516
+ exec(`netsh advfirewall firewall show rule name="${ruleName}"`, { windowsHide: true }, (err, stdout) => {
517
+ if (!err && stdout.includes(ruleName)) {
518
+ // Rule exists, check if it's enabled
519
+ if (stdout.includes('Enabled:') && stdout.includes('Yes')) {
520
+ console.log(' Firewall: Allowed');
521
+ return;
522
+ }
523
+ }
524
+
525
+ // No rule found — prompt the user to allow it
526
+ console.log('');
527
+ console.log(' !! Firewall rule not found for LAN Transfer.');
528
+ console.log(' !! Device discovery requires UDP/TCP access through Windows Firewall.');
529
+ console.log(' !! Attempting to add firewall rule (requires Admin)...');
530
+ console.log('');
531
+
532
+ // Try to add the rule via elevated PowerShell — this triggers a UAC prompt
533
+ const addCmd = [
534
+ `netsh advfirewall firewall add rule name="${ruleName}" dir=in action=allow protocol=UDP localport=${DISCOVERY_PORT} program="${nodeExe}" enable=yes`,
535
+ `netsh advfirewall firewall add rule name="${ruleName}" dir=in action=allow protocol=TCP localport=${TRANSFER_PORT} program="${nodeExe}" enable=yes`,
536
+ ].join(' & ');
537
+
538
+ const psCmd = `powershell -Command "Start-Process cmd -ArgumentList '/c ${addCmd.replace(/"/g, '\\"')}' -Verb RunAs -Wait"`;
539
+
540
+ exec(psCmd, { windowsHide: false, timeout: 60_000 }, (err2) => {
541
+ if (err2) {
542
+ console.log(' !! Could not add firewall rule. Discovery may not work.');
543
+ console.log(' !! To fix manually:');
544
+ console.log(' !! 1. Open Windows Security > Firewall & network protection');
545
+ console.log(' !! 2. Click "Allow an app through firewall"');
546
+ console.log(' !! 3. Add Node.js and allow Private + Public networks');
547
+ console.log('');
548
+ } else {
549
+ console.log(' Firewall: Rule added successfully! Discovery should work now.');
550
+ }
551
+ });
552
+ });
553
+ }
554
+
481
555
  // ──────────────────────────────────────────────────────────────────────────────
482
556
  // Start
483
557
  // ──────────────────────────────────────────────────────────────────────────────
@@ -498,6 +572,9 @@ server.listen(HTTP_PORT, () => {
498
572
  console.log(` UI: http://localhost:${HTTP_PORT}`);
499
573
  console.log('');
500
574
 
575
+ // Check firewall on Windows
576
+ checkWindowsFirewall();
577
+
501
578
  // Auto-open browser
502
579
  const url = `http://localhost:${HTTP_PORT}`;
503
580
  if (process.platform === 'win32') exec(`start "" "${url}"`);