Last active
November 1, 2018 13:14
-
-
Save JLChnToZ/eb928f52678b5660f833a6d1d4858557 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createSocket, Socket } from 'dgram'; | |
import { isIP } from 'net'; | |
import { createServer } from 'http'; | |
import { URL } from 'url'; | |
import { lookup as dnsLookup } from 'dns'; | |
import { unescape as unescapeQuery } from 'querystring'; | |
import { promisify } from 'util'; | |
const macAddressFormat = /[^0-9A-F]/g; | |
const dnsLookupAsync = promisify(dnsLookup); | |
async function resolveHostPort(hostPort: string) { | |
// Make the host/port looks like a url for processing... | |
const url = new URL(`http://${hostPort || '255.255.255.255'}`); | |
const port = Number.parseInt(url.port); | |
let { hostname } = url; | |
if(!isIP(url.hostname)) | |
try { | |
hostname = (await dnsLookupAsync(hostname)).address; | |
} catch {} | |
return { hostname, port }; | |
} | |
/** Create WOL magic packet for wake up specified device */ | |
function createMagicPacket(mac: number | string) { | |
if(typeof mac === 'string') { | |
if(mac.length <= 10) | |
// Base-36 encoded MAC address | |
mac = Number.parseInt(mac, 36); | |
else | |
// Normal form MAC address string | |
mac = Number.parseInt(mac.replace(macAddressFormat, ''), 16); | |
} | |
if(!Number.isInteger(mac) || mac < 0 || mac > 281474976710655) | |
throw new TypeError('Invalid MAC address'); | |
// 6 times 0xFF, followed by 16 times MAC address, total 102 bytes. | |
const magic = Buffer.allocUnsafe(102); | |
magic.fill(0xFF, 0, 6); | |
for(let i = 1; i <= 16; i++) | |
magic.writeUIntBE(mac, 6 * i, 6); | |
return magic; | |
} | |
/** Wake up the device */ | |
function wakeup(mac: number | string, port?: number, host?: string) { | |
return new Promise<void>((resolve, reject) => { | |
let client: Socket; | |
const magic = createMagicPacket(mac); | |
switch(isIP(host || '')) { | |
// Default host is ipv4 broadcast | |
default: host = '255.255.255.255'; | |
case 4: client = createSocket('udp4'); break; | |
case 6: client = createSocket('udp6'); break; | |
} | |
client.send(magic, 0, magic.length, port || 9, host, err => { | |
try { | |
if(err) reject(err); | |
else resolve(); | |
} finally { | |
client.close(); | |
} | |
}); | |
client.on('error', err => { | |
try { | |
reject(err); | |
} finally { | |
client.close(); | |
} | |
}); | |
client.once('listening', onSocketListen); | |
}); | |
} | |
function onSocketListen(this: Socket) { | |
this.setBroadcast(true); | |
} | |
const macMatcher = /^((?:[0-9A-F:.-]{12,17})|(?:[0-9A-Z]{1,10}))$/i; | |
createServer(async(req, res) => { | |
const parsedUrl = req.url && req.url.split('/'); | |
let targetMAC: string; | |
if(!parsedUrl || !macMatcher.test(targetMAC = unescapeQuery(parsedUrl[1]))) { | |
res.writeHead(404, 'Not found'); | |
res.end(); | |
return; | |
} | |
res.writeHead(200, { 'Content-Type': 'application/json' }); | |
try { | |
const { hostname, port } = await resolveHostPort(unescapeQuery(parsedUrl[2] || '')); | |
await wakeup(targetMAC, port, hostname); | |
res.write(JSON.stringify({ success: true })); | |
} catch(e) { | |
res.write(JSON.stringify({ success: false, error: e.message || e })); | |
console.error(e.stack || e); | |
} | |
res.end(); | |
}).listen(9999); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage: Assume this script is running on a server with domain
example.com
and you want to wake up a device with MAC addressAA:BB:CC:DD:EE:FF
within the same network of that server, you may do the request remotely like this:http://example.com:9999/AA:BB:CC:DD:EE:FF
: Use broadcast IPv4 and UDP 9.http://example.com:9999/AA:BB:CC:DD:EE:FF/192.168.1.1
: Send to specified IP address and default UDP (9).http://example.com:9999/AA:BB:CC:DD:EE:FF/192.168.1.1/7
: Send to specified IP address and UDP.http://example.com:9999/AA:BB:CC:DD:EE:FF//7
: Use broadcast IPv4 and specified UDP.http://example.com:9999/1ujj0ob3lr
: MAC address can be base-36 encoded.