mirror of
https://github.com/k1tbyte/Wand-Enhancer.git
synced 2026-08-28 17:01:04 +00:00
13759b1db6
- reduce ASAR IO overhead with streamed archive reads, buffered copies, faster relative-path handling and placeholder integrity records - fix in-place app.asar.unpacked packing/extraction self-copy cases that caused locked-file failures - tighten JS patch discovery with candidate bundle filters and search hints - require prebuilt remote-panel dist artifacts and clean up embedded bridge/script packaging - add unified build entrypoints for PowerShell, cmd and bash and move native CMake output under .tmp - add release metadata validation, changelog section extraction, pre-commit hook and GitHub Actions validation/release pipelines - make CHANGELOG the source of truth for release notes and document the tag-driven release flow - add updater release notes UI with latest/full changelog loading and localize the new update strings - modularize bridge renderer scripts, add installed apps and game status sync, and support remote launch/stop commands - centralize bridge protocol, IPC and WebSocket constants and improve LAN IP selection for QR pairing - refactor remote panel controls/state enums, persist accent color, polish library/session UI and refresh assets
167 lines
4.3 KiB
JavaScript
167 lines
4.3 KiB
JavaScript
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
|
|
const { REMOTE_BASE_PATH } = require('./constants.cjs');
|
|
|
|
const IPV4_OCTET_PATTERN = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
const PHYSICAL_INTERFACE_NAME_PATTERN = /(?:ethernet|wi-?fi|wireless|wlan|lan|local area)/i;
|
|
const VIRTUAL_INTERFACE_NAME_PATTERN = /(?:bluetooth|container|docker|hamachi|hyper-v|loopback|npcap|pseudo|tap|tailscale|teredo|tunnel|tun|virtual|vmware|vbox|virtualbox|vpn|wireguard|wsl|zerotier)/i;
|
|
const VIRTUAL_MAC_PREFIXES = new Set([
|
|
'00:05:69',
|
|
'00:0c:29',
|
|
'00:15:5d',
|
|
'00:16:3e',
|
|
'00:1c:14',
|
|
'00:50:56',
|
|
'08:00:27',
|
|
'52:54:00',
|
|
]);
|
|
|
|
function contentTypeFor(filePath) {
|
|
const extension = path.extname(filePath).toLowerCase();
|
|
switch (extension) {
|
|
case '.html':
|
|
return 'text/html; charset=utf-8';
|
|
case '.js':
|
|
case '.cjs':
|
|
return 'application/javascript; charset=utf-8';
|
|
case '.css':
|
|
return 'text/css; charset=utf-8';
|
|
case '.json':
|
|
return 'application/json; charset=utf-8';
|
|
case '.svg':
|
|
return 'image/svg+xml';
|
|
default:
|
|
return 'application/octet-stream';
|
|
}
|
|
}
|
|
|
|
function getAdvertisedUrls(port) {
|
|
const candidates = [];
|
|
const interfaces = os.networkInterfaces();
|
|
let index = 0;
|
|
|
|
for (const [name, entries] of Object.entries(interfaces)) {
|
|
if (!entries) {
|
|
continue;
|
|
}
|
|
|
|
for (const entry of entries) {
|
|
if (!isUsableIpv4Entry(entry)) {
|
|
continue;
|
|
}
|
|
|
|
candidates.push({
|
|
index,
|
|
score: scoreIpv4Entry(name, entry),
|
|
url: `http://${entry.address}:${port}${REMOTE_BASE_PATH}`,
|
|
});
|
|
index += 1;
|
|
}
|
|
}
|
|
|
|
const urls = candidates
|
|
.sort((left, right) => right.score - left.score || left.index - right.index)
|
|
.map((candidate) => candidate.url);
|
|
|
|
urls.unshift(`http://localhost:${port}${REMOTE_BASE_PATH}`);
|
|
return Array.from(new Set(urls));
|
|
}
|
|
|
|
function isUsableIpv4Entry(entry) {
|
|
return Boolean(entry && !entry.internal && isIpv4Family(entry.family) && parseIpv4(entry.address));
|
|
}
|
|
|
|
function isIpv4Family(family) {
|
|
return family === 'IPv4' || family === 4;
|
|
}
|
|
|
|
function scoreIpv4Entry(name, entry) {
|
|
const octets = parseIpv4(entry.address);
|
|
let score = 0;
|
|
|
|
if (isPrivateIpv4(octets)) {
|
|
score += 1000;
|
|
}
|
|
|
|
if (octets[0] === 192 && octets[1] === 168) {
|
|
score += 40;
|
|
} else if (octets[0] === 10) {
|
|
score += 30;
|
|
} else if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
|
|
score += 20;
|
|
}
|
|
|
|
if (PHYSICAL_INTERFACE_NAME_PATTERN.test(name)) {
|
|
score += 120;
|
|
}
|
|
|
|
if (VIRTUAL_INTERFACE_NAME_PATTERN.test(name)) {
|
|
score -= 700;
|
|
}
|
|
|
|
if (isVirtualMac(entry.mac)) {
|
|
score -= 450;
|
|
}
|
|
|
|
if (isLinkLocalIpv4(octets)) {
|
|
score -= 1200;
|
|
}
|
|
|
|
if (octets[3] === 1 || octets[3] === 254) {
|
|
score -= 25;
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
function parseIpv4(address) {
|
|
if (typeof address !== 'string') {
|
|
return null;
|
|
}
|
|
|
|
const match = address.match(IPV4_OCTET_PATTERN);
|
|
if (!match) {
|
|
return null;
|
|
}
|
|
|
|
const octets = match.slice(1).map((part) => Number(part));
|
|
return octets.every((octet) => Number.isInteger(octet) && octet >= 0 && octet <= 255) ? octets : null;
|
|
}
|
|
|
|
function isPrivateIpv4(octets) {
|
|
return octets[0] === 10 || (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) || (octets[0] === 192 && octets[1] === 168);
|
|
}
|
|
|
|
function isLinkLocalIpv4(octets) {
|
|
return octets[0] === 169 && octets[1] === 254;
|
|
}
|
|
|
|
function isVirtualMac(mac) {
|
|
if (typeof mac !== 'string') {
|
|
return false;
|
|
}
|
|
|
|
return VIRTUAL_MAC_PREFIXES.has(mac.toLowerCase().slice(0, 8));
|
|
}
|
|
|
|
function serveFile(response, filePath) {
|
|
try {
|
|
const content = fs.readFileSync(filePath);
|
|
response.writeHead(200, {
|
|
'Content-Type': contentTypeFor(filePath),
|
|
'Cache-Control': 'no-store',
|
|
});
|
|
response.end(content);
|
|
} catch {
|
|
response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
response.end('Not found');
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
getAdvertisedUrls,
|
|
serveFile,
|
|
};
|