-
-
Notifications
You must be signed in to change notification settings - Fork 40
/
index.js
160 lines (136 loc) · 4.25 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
const url = require('url')
const http = require('http')
const os = require('os')
const { Client: SSDPClient } = require('node-ssdp')
const ip = require('ip')
const debugFactory = require('debug')
const WemoClient = require('./client.js')
const universalify = require('./frompromise.js')
const debug = debugFactory('wemo-client')
class Wemo {
#port = 0
#listenInterface
/** @type {Map<string, WemoClient>} */
#clients = new Map()
#ssdpClient
#server
constructor (opts = {}) {
this.#port = opts.port || 0
this.#listenInterface = opts.listen_interface
this._listen()
this.#ssdpClient = new SSDPClient(opts.discover_opts || {})
}
async load (setupUrl) {
const location = url.parse(setupUrl)
const json = await WemoClient.request({
method: 'GET',
host: location.hostname,
port: location.port,
path: location.path,
})
const device = json.root.device
device.host = location.hostname
device.port = location.port
device.callbackURL = this.getCallbackURL({
clientHostname: location.hostname
})
// Return devices only once!
if (!this.#clients.has(device.UDN) || this.#clients.get(device.UDN).error) {
debug('Found device: %j', json)
return device
}
}
discover (cb) {
const handleResponse = (msg, statusCode, rinfo) => {
if (msg.ST && msg.ST === 'urn:Belkin:service:basicevent:1') {
this.load(msg.LOCATION).then(cb)
}
}
this.#ssdpClient.removeAllListeners('response')
this.#ssdpClient.on('response', handleResponse)
this.#ssdpClient.search('urn:Belkin:service:basicevent:1')
}
_listen () {
const serverCallback = err => {
if (err) {
throw err
}
}
this.#server = http.createServer(this._handleRequest.bind(this))
this.#listenInterface
? this.#server.listen(this.#port, this.getLocalInterfaceAddress(), serverCallback)
: this.#server.listen(this.#port, serverCallback)
}
_handleRequest (req, res) {
let body = ''
const udn = req.url.substring(1)
if (req.method == 'NOTIFY' && this.#clients.get(udn)) {
req.on('data', chunk => {
body += chunk.toString()
})
req.on('end', () => {
debug('Incoming Request for %s: %s', udn, body)
this.#clients.get(udn).handleCallback(body)
res.writeHead(204)
res.end()
})
} else {
debug('Received request for unknown device: %s', udn)
res.writeHead(404)
res.end()
}
}
getLocalInterfaceAddress (targetNetwork) {
let interfaces = os.networkInterfaces()
if (this.#listenInterface) {
if (interfaces[this.#listenInterface]) {
interfaces = [interfaces[this.#listenInterface]]
} else {
throw new Error('Unable to find interface ' + this.#listenInterface)
}
}
const addresses = []
for (const k in interfaces) {
for (const k2 in interfaces[k]) {
const address = interfaces[k][k2]
if (address.family === 'IPv4' && !address.internal) {
if (targetNetwork && ip.subnet(address.address, address.netmask).contains(targetNetwork)) {
addresses.unshift(address.address)
} else {
addresses.push(address.address)
}
}
}
}
return addresses.shift()
}
getCallbackURL (opts = {}) {
if (!this._callbackURL) {
const port = this.#server.address().port
const host = this.getLocalInterfaceAddress(opts.clientHostname)
this._callbackURL = `http://${host}:${port}`
}
return this._callbackURL
}
client (device) {
if (this.#clients[device.UDN] && !this.#clients[device.UDN].error) {
return this.#clients.get(device.UDN)
}
const client = new WemoClient(device)
this.#clients.set(device.UDN, client)
return client
}
}
Wemo.DEVICE_TYPE = {
Bridge: 'urn:Belkin:device:bridge:1',
Switch: 'urn:Belkin:device:controllee:1',
Motion: 'urn:Belkin:device:sensor:1',
Maker: 'urn:Belkin:device:Maker:1',
Insight: 'urn:Belkin:device:insight:1',
LightSwitch: 'urn:Belkin:device:lightswitch:1',
Dimmer: 'urn:Belkin:device:dimmer:1',
Humidifier: 'urn:Belkin:device:Humidifier:1',
HeaterB: 'urn:Belkin:device:HeaterB:1'
}
Wemo.prototype.load = universalify(Wemo.prototype.load)
module.exports = Wemo