@balaji003/lantransfer 1.0.2 → 1.0.4
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 +3 -3
- package/package.json +1 -1
- package/public/index.html +26 -3
- package/server.js +77 -2
package/bin/cli.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
require('../server.js');
|
package/package.json
CHANGED
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">⇌ LAN Transfer</div>
|
|
208
219
|
<div class="device-info" id="device-info"></div>
|
|
209
220
|
</div>
|
|
210
|
-
<div class="
|
|
211
|
-
<div class="toggle
|
|
212
|
-
|
|
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
|
+
⏻ 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
|
-
|
|
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,53 @@ 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
|
|
516
|
+
exec(`netsh advfirewall firewall show rule name="${ruleName}"`, { windowsHide: true }, (err, stdout) => {
|
|
517
|
+
if (!err && stdout.includes(ruleName)) {
|
|
518
|
+
if (stdout.includes('Enabled:') && stdout.includes('Yes')) {
|
|
519
|
+
console.log(' Firewall: Allowed');
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// No rule — prompt user via UAC elevation
|
|
525
|
+
console.log('');
|
|
526
|
+
console.log(' !! Firewall rule not found for LAN Transfer.');
|
|
527
|
+
console.log(' !! Device discovery requires UDP/TCP through Windows Firewall.');
|
|
528
|
+
console.log(' !! Requesting permission (UAC prompt)...');
|
|
529
|
+
console.log('');
|
|
530
|
+
|
|
531
|
+
const addCmd = [
|
|
532
|
+
`netsh advfirewall firewall add rule name="${ruleName}" dir=in action=allow protocol=UDP localport=${DISCOVERY_PORT} program="${nodeExe}" enable=yes`,
|
|
533
|
+
`netsh advfirewall firewall add rule name="${ruleName}" dir=in action=allow protocol=TCP localport=${TRANSFER_PORT} program="${nodeExe}" enable=yes`,
|
|
534
|
+
].join(' & ');
|
|
535
|
+
|
|
536
|
+
const psCmd = `powershell -Command "Start-Process cmd -ArgumentList '/c ${addCmd.replace(/"/g, '\\"')}' -Verb RunAs -Wait"`;
|
|
537
|
+
|
|
538
|
+
exec(psCmd, { windowsHide: false, timeout: 60_000 }, (err2) => {
|
|
539
|
+
if (err2) {
|
|
540
|
+
console.log(' !! Firewall permission denied. Discovery will NOT work.');
|
|
541
|
+
console.log(' !! To fix manually:');
|
|
542
|
+
console.log(' !! 1. Open Windows Security > Firewall & network protection');
|
|
543
|
+
console.log(' !! 2. Click "Allow an app through firewall"');
|
|
544
|
+
console.log(' !! 3. Add Node.js and allow Private + Public networks');
|
|
545
|
+
console.log('');
|
|
546
|
+
} else {
|
|
547
|
+
console.log(' Firewall: Rule added! Discovery should work now.');
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
481
553
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
482
554
|
// Start
|
|
483
555
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
@@ -498,6 +570,9 @@ server.listen(HTTP_PORT, () => {
|
|
|
498
570
|
console.log(` UI: http://localhost:${HTTP_PORT}`);
|
|
499
571
|
console.log('');
|
|
500
572
|
|
|
573
|
+
// Check firewall on Windows
|
|
574
|
+
checkWindowsFirewall();
|
|
575
|
+
|
|
501
576
|
// Auto-open browser
|
|
502
577
|
const url = `http://localhost:${HTTP_PORT}`;
|
|
503
578
|
if (process.platform === 'win32') exec(`start "" "${url}"`);
|