/** * Wake-on-LAN service — sends a magic packet to wake a sleeping Mac. * * The magic packet is 6 bytes of 0xFF followed by the target MAC address * repeated 16 times, sent as a UDP broadcast on port 9. */ import dgram from "react-native-udp"; function parseMac(mac: string): number[] { const parts = mac .replace(/[:-]/g, "") .match(/.{2}/g); if (!parts || parts.length !== 6) { throw new Error(`Invalid MAC address: ${mac}`); } return parts.map((h) => parseInt(h, 16)); } function buildMagicPacket(mac: string): Uint8Array { const macBytes = parseMac(mac); const packet = new Uint8Array(102); // 6 bytes of 0xFF for (let i = 0; i < 6; i++) { packet[i] = 0xff; } // MAC address repeated 16 times for (let i = 0; i < 16; i++) { const offset = 6 + i * 6; for (let j = 0; j < 6; j++) { packet[offset + j] = macBytes[j]; } } return packet; } /** * Send a Wake-on-LAN magic packet to the given MAC address. * Sends to both the subnet broadcast (derived from host) and 255.255.255.255. */ export async function sendWol(mac: string, host?: string): Promise { const packet = buildMagicPacket(mac); // Derive broadcast address from host IP (replace last octet with 255) const broadcastAddresses = ["255.255.255.255"]; if (host) { const parts = host.split("."); if (parts.length === 4) { parts[3] = "255"; const subnetBroadcast = parts.join("."); if (!broadcastAddresses.includes(subnetBroadcast)) { broadcastAddresses.push(subnetBroadcast); } } } const TIMEOUT_MS = 5000; return new Promise((resolve, reject) => { let settled = false; const settle = (fn: () => void) => { if (settled) return; settled = true; clearTimeout(timer); fn(); }; const timer = setTimeout(() => { settle(() => { try { socket.close(); } catch { /* ignore */ } reject(new Error("WoL timed out — magic packet may not have been sent")); }); }, TIMEOUT_MS); const socket = dgram.createSocket({ type: "udp4" }); socket.once("error", (err: Error) => { settle(() => { try { socket.close(); } catch { /* ignore */ } reject(err); }); }); socket.bind(0, () => { try { socket.setBroadcast(true); } catch { // Some platforms don't support setBroadcast — continue anyway } let pending = broadcastAddresses.length; for (const addr of broadcastAddresses) { socket.send(packet, 0, packet.length, 9, addr, (err?: Error) => { if (err) { settle(() => { try { socket.close(); } catch { /* ignore */ } reject(err); }); return; } pending--; if (pending === 0) { settle(() => { try { socket.close(); } catch { /* ignore */ } resolve(); }); } }); } }); }); } /** * Validate a MAC address string. */ export function isValidMac(mac: string): boolean { return /^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/.test(mac.trim()); }