homebridge-lib 6.3.6 → 6.3.8

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/lib/UpnpClient.js DELETED
@@ -1,217 +0,0 @@
1
- // homebridge-lib/lib/UpnpClient.js
2
- //
3
- // Library for Homebridge plugins.
4
- // Copyright © 2018-2023 Erik Baauw. All rights reserved.
5
-
6
- 'use strict'
7
-
8
- const homebridgeLib = require('../index')
9
-
10
- const dgram = require('dgram')
11
- const events = require('events')
12
-
13
- const listener = {}
14
-
15
- // Convert raw UPnP message to message object.
16
- function convert (rawMessage) {
17
- const message = {}
18
- const lines = rawMessage.toString().trim().split('\r\n')
19
- if (lines && lines[0]) {
20
- message.status = lines[0]
21
- for (const line of lines) {
22
- const fields = line.split(': ')
23
- if (fields.length === 2) {
24
- message[fields[0].toLowerCase()] = fields[1]
25
- }
26
- }
27
- }
28
- return message
29
- }
30
-
31
- /** Universal Plug and Play client.
32
- * @extends EventEmitter
33
- */
34
- class UpnpClient extends events.EventEmitter {
35
- /** Create a new instance of a Universal Plug and Play client.
36
- * @param {object} params - Paramters.
37
- * @param {function} [params.filter=() => { return true }] - Function to
38
- * filter which UPnP messages should result in a
39
- * {@link UpnpClient#event:deviceAlive deviceAlive} or a
40
- * {@link UpnpClient#event:deviceFound deviceFound} event.
41
- * @param {integer} [params.timemout=5] - Timeout (in seconds) for
42
- * {@link UpnpClient@search search()} to listen for responses.
43
- * @param {string} [params.class='upnp:rootdevice'] - Filter on class which UPnP
44
- * messages should result in a
45
- * {@link UpnpClient#event:deviceAlive deviceAlive} or a
46
- * {@link UpnpClient#event:deviceFound deviceFound} event.
47
- */
48
- constructor (params = {}) {
49
- super()
50
- this._options = {
51
- filter: () => { return true },
52
- hostname: '239.255.255.250',
53
- port: 1900,
54
- timeout: 5,
55
- class: 'upnp:rootdevice'
56
- }
57
- const optionParser = new homebridgeLib.OptionParser(this._options)
58
- optionParser.functionKey('filter')
59
- // optionParser.hostKey() // TODO: need global listener per hostname.
60
- optionParser.stringKey('class', true)
61
- optionParser.intKey('timeout', 1, 60)
62
- optionParser.parse(params)
63
- this.requestId = 0
64
- }
65
-
66
- _onError (error) {
67
- listener.socket = null
68
- /** Emitted in case of error.
69
- * @event UpnpClient#error
70
- * @param {Error} error - The error.
71
- */
72
- this.emit('error', error)
73
- }
74
-
75
- _onListening () {
76
- if (listener.init === this) {
77
- listener.host = listener.socket.address().address +
78
- ':' + listener.socket.address().port
79
- listener.socket.addMembership(this._options.hostname)
80
- delete listener.init
81
- }
82
- /** Emitted when listening to UPnP alive broadcasts.
83
- * @event UpnpClient#listening
84
- * @param {string} host - IP address and port listening on.
85
- */
86
- this.emit('listening', listener.host)
87
- }
88
-
89
- _onMessage (buffer, rinfo) {
90
- const rawMessage = buffer.toString().trim()
91
- const message = convert(rawMessage)
92
- if (
93
- message.status !== 'NOTIFY * HTTP/1.1' ||
94
- message.nts !== 'ssdp:alive'
95
- ) {
96
- return
97
- }
98
- if (
99
- this._options.class !== 'ssdp:all' &&
100
- message.nt !== this._options.class
101
- ) {
102
- return
103
- }
104
- if (this._options.filter(message)) {
105
- /** Emitted for each alive message received, that passes the filers.
106
- * @event UpnpClient#deviceAlive
107
- * @param {string} address - IP address of the device.
108
- * @param {object} message - The parsed message.
109
- * @param {string} rawMessage - The raw message.
110
- */
111
- this.emit('deviceAlive', rinfo.address, message, rawMessage)
112
- }
113
- }
114
-
115
- /** Listen for UPnP alive broadcast messages.
116
- */
117
- async listen () {
118
- if (listener.socket == null) {
119
- listener.socket = dgram.createSocket({ type: 'udp4', reuseAddr: true })
120
- listener.init = this
121
- listener.socket.bind(this._options.port)
122
- } else if (listener.init != null) {
123
- const [host] = await events.once(listener.init, 'listening')
124
- this.emit('listening', host)
125
- } else {
126
- this.emit('listening', listener.host)
127
- }
128
-
129
- listener.socket
130
- .on('error', this._onError.bind(this))
131
- .on('listening', this._onListening.bind(this))
132
- .on('message', this._onMessage.bind(this))
133
- }
134
-
135
- /** Stop listening for UPnP alive broadcast messages.
136
- */
137
- async stopListen () {
138
- if (listener.socket != null) {
139
- listener.socket
140
- .removeListener('error', this._onError.bind(this))
141
- .removeListener('listening', this._onListening.bind(this))
142
- .removeListener('message', this._onMessage.bind(this))
143
- this.emit('stopListening', listener.host)
144
- }
145
- }
146
-
147
- /** Issue a UPnP search message and listen for responses.
148
- */
149
- search () {
150
- const socket = dgram.createSocket({ type: 'udp4' })
151
- const request = Buffer.from([
152
- 'M-SEARCH * HTTP/1.1',
153
- `HOST: ${this._options.hostname}:${this._options.port}`,
154
- 'MAN: "ssdp:discover"',
155
- `MX: ${this._options.timeout}`,
156
- `ST: ${this._options.class}`,
157
- ''
158
- ].join('\r\n'))
159
- let host
160
-
161
- socket
162
- .on('error', (error) => {
163
- this.emit('error', error)
164
- })
165
- .on('listening', () => {
166
- host = socket.address().address + ':' + socket.address().port
167
- /** Emitted when searching for UPnP devices.
168
- * @event UpnpClient#searching
169
- * @param {string} host - IP address and port listening on.
170
- */
171
- this.emit('searching', host)
172
- })
173
- .on('message', (buffer, rinfo) => {
174
- const rawMessage = buffer.toString().trim()
175
- const message = convert(rawMessage)
176
- if (message.status !== 'HTTP/1.1 200 OK') {
177
- return
178
- }
179
- if (
180
- this._options.class !== 'ssdp:all' &&
181
- message.st !== this._options.class
182
- ) {
183
- return
184
- }
185
- if (this._options.filter(message)) {
186
- /** Emitted for each response received, that passes the filers.
187
- * @event UpnpClient#deviceFound
188
- * @param {string} address - IP address of the device.
189
- * @param {object} message - The parsed message.
190
- * @param {string} rawMessage - The raw message.
191
- */
192
- this.emit('deviceFound', rinfo.address, message, rawMessage)
193
- }
194
- })
195
-
196
- const requestInfo = {
197
- host: this._options.hostname + ':' + this._options.port,
198
- id: ++this.requestId,
199
- method: 'M-SEARCH',
200
- resource: '*'
201
- }
202
- this.emit('request', requestInfo)
203
- socket.send(
204
- request, 0, request.length, this._options.port, this._options.hostname
205
- )
206
- setTimeout(() => {
207
- socket.close()
208
- /** Emitted when searching has finished.
209
- * @event UpnpClient#searchDone
210
- * @param {string} host - IP address and port listening on.
211
- */
212
- this.emit('searchDone', host)
213
- }, this._options.timeout * 1000)
214
- }
215
- }
216
-
217
- module.exports = UpnpClient