homebridge-lib 6.3.7 → 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
package/lib/UpnpTool.js DELETED
@@ -1,148 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // homebridge-lib/cli/upnp.js
4
- //
5
- // Logger for UPnP device announcements.
6
- // Copyright © 2018-2023 Erik Baauw. All rights reserved.
7
-
8
- 'use strict'
9
-
10
- const homebridgeLib = require('../index')
11
-
12
- const { b, u } = homebridgeLib.CommandLineTool
13
-
14
- const usage = `${b('upnp')} [${b('-hVDadnprsz')}] [${b('-c')} ${u('class')}] [${b('-t')} ${u('timeout')}]`
15
- const help = `UPnP tool.
16
-
17
- Search for UPnP devices and print found devices as JSON.
18
- When running as daemon or service, log UPnP alive broadcasts as JSON.
19
-
20
- Usage: ${usage}
21
-
22
- Parameters:
23
- ${b('-h')}, ${b('--help')}
24
- Print this help and exit.
25
-
26
- ${b('-V')}, ${b('--version')}
27
- Print version and exit.
28
-
29
- ${b('-D')}, ${b('--debug')}
30
- Print debug messages.
31
-
32
- ${b('-a')}, ${b('--all')}
33
- Short for ${b('-c ssdp:all')}.
34
-
35
- ${b('-c')} ${u('class')}, ${b('--class=')}${u('class')}
36
- Search for ${u('class')} instead of default ${b('ssdp:rootdevice')}.
37
-
38
- ${b('-d')}, ${b('--daemon')}
39
- Run as daemon. Listen for UPnP alive broadcasts instead of searching.
40
-
41
- ${b('-n')}, ${b('--noWhiteSpace')}
42
- Do not include spaces nor newlines in JSON output.
43
-
44
- ${b('-p')}, ${b('--hue')}
45
- Search for Philips Hue bridges and/or deCONZ gateways.
46
-
47
- ${b('-r')}, ${b('--raw')}
48
- Do not parse messages, output raw message data instead of JSON.
49
-
50
- ${b('-s')}, ${b('--service')}
51
- Run as daemon. Listen for UPnP alive broadcasts instead of searching.
52
-
53
- ${b('-t')} ${u('timeout')}, ${b('--timeout=')}${u('timeout')}
54
- Search for ${u('timeout')} seconds instead of default ${b('5')}.
55
-
56
- ${b('-z')}, ${b('--sonos')}
57
- Search for Sonos Zone Players.`
58
-
59
- class UpnpTool extends homebridgeLib.CommandLineTool {
60
- constructor () {
61
- super()
62
- this.usage = usage
63
- this.options = {
64
- noWhiteSpace: false
65
- }
66
- this.upnp = {}
67
- }
68
-
69
- parseArguments () {
70
- const parser = new homebridgeLib.CommandLineParser()
71
- parser
72
- .help('h', 'help', help)
73
- .version('V', 'version')
74
- .flag('D', 'debug', () => { this.setOptions({ debug: true }) })
75
- .flag('a', 'all', (key) => { this.upnp.class = 'ssdp:all' })
76
- .option('c', 'class', (value, key) => { this.upnp.class = value })
77
- .flag('d', 'daemon', (key) => { this.options.mode = 'daemon' })
78
- .flag('n', 'noWhiteSpace', () => { this.options.noWhiteSpace = true })
79
- .flag('r', 'raw', (key) => { this.options.raw = true })
80
- .flag('s', 'service', (key) => { this.options.mode = 'service' })
81
- .option('t', 'timeout', (value, key) => {
82
- this.upnp.timeout = homebridgeLib.OptionParser.toInt(
83
- 'timeout', value, 1, 60, true
84
- )
85
- })
86
- .flag('p', 'hue', (key) => {
87
- this.upnp.filter = (message) => {
88
- return /^[0-9A-F]{16}$/.test(message['hue-bridgeid'])
89
- }
90
- })
91
- .flag('z', 'sonos', (key) => {
92
- this.upnp.class = 'urn:schemas-upnp-org:device:ZonePlayer:1'
93
- })
94
- .parse()
95
- }
96
-
97
- async destroy () {
98
- if (this.upnpClient == null) {
99
- return
100
- }
101
- return this.upnpClient.stopListen()
102
- }
103
-
104
- async main () {
105
- try {
106
- this.parseArguments()
107
- const jsonFormatter = new homebridgeLib.JsonFormatter({
108
- noWhiteSpace: this.options.noWhiteSpace
109
- })
110
- this.upnpClient = new homebridgeLib.UpnpClient(this.upnp)
111
- this.upnpClient
112
- .on('error', (error) => { this.error(error) })
113
- .on('listening', (host) => { this.debug('listening on %s', host) })
114
- .on('stopListening', (host) => { this.debug('stopped listening on %s', host) })
115
- .on('deviceAlive', (address, obj, message) => {
116
- if (!this.options.raw) {
117
- message = jsonFormatter.stringify(obj)
118
- }
119
- this.log('%s: %s', address, message)
120
- })
121
- .on('searching', (host) => { this.debug('listening on %s', host) })
122
- .on('request', (request) => {
123
- this.debug(
124
- '%s: request %d: %s %s', request.host, request.id,
125
- request.method, request.resource
126
- )
127
- })
128
- .on('searchDone', (host) => { this.debug('search done') })
129
- .on('deviceFound', (address, obj, message) => {
130
- if (!this.options.raw) {
131
- message = jsonFormatter.stringify(obj)
132
- }
133
- this.print('%s: %s', address, message)
134
- })
135
-
136
- if (this.options.mode) {
137
- this.setOptions({ mode: this.options.mode })
138
- this.upnpClient.listen()
139
- } else {
140
- this.upnpClient.search()
141
- }
142
- } catch (error) {
143
- await this.fatal(error)
144
- }
145
- }
146
- }
147
-
148
- module.exports = UpnpTool