Skip to content

Commit 1004c78

Browse files
authored
feat: add unixSocket option and +unix suffix support to protocol (#1874)
* fix: consider path when protocol is ws/wss * fix: add test * feat: add `unixSocket` option and `+unix` suffix support to protocol * fix: readme * fix: minor refactor * fix: validate protocol only when parsing url * fix: use object.assign * fix: edge cases * style: add comments
1 parent 7d5820c commit 1004c78

File tree

4 files changed

+67
-19
lines changed

4 files changed

+67
-19
lines changed

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ Hello mqtt
124124

125125
MQTT.js can be used in React Native applications. To use it, see the [React Native example](https://github.com/MaximoLiberata/react-native-mqtt.js-example)
126126

127-
128127
If you want to run your own MQTT broker, you can use
129128
[Mosquitto](http://mosquitto.org) or
130129
[Aedes-cli](https://github.com/moscajs/aedes-cli), and launch it.
@@ -354,7 +353,9 @@ Connects to the broker specified by the given url and options and
354353
returns a [Client](#client).
355354

356355
The URL can be on the following protocols: 'mqtt', 'mqtts', 'tcp',
357-
'tls', 'ws', 'wss', 'wxs', 'alis'. The URL can also be an object as returned by
356+
'tls', 'ws', 'wss', 'wxs', 'alis'. If you are trying to connect to a unix socket just append the `+unix` suffix to the protocol (ex: `mqtt+unix`). This will set the `unixSocket` property automatically.
357+
358+
The URL can also be an object as returned by
358359
[`URL.parse()`](http://nodejs.org/api/url.html#url_url_parse_urlstr_parsequerystring_slashesdenotehost),
359360
in that case the two objects are merged, i.e. you can pass a single
360361
object with both the URL and the connect options.
@@ -466,6 +467,7 @@ The arguments are:
466467
- `log`: custom log function. Default uses [debug](https://www.npmjs.com/package/debug) package.
467468
- `manualConnect`: prevents the constructor to call `connect`. In this case after the `mqtt.connect` is called you should call `client.connect` manually.
468469
- `timerVariant`: defaults to `auto`, which tries to determine which timer is most appropriate for you environment, if you're having detection issues, you can set it to `worker` or `native`
470+
- `unixSocket`: if you want to connect to a unix socket, set this to true
469471

470472
In case mqtts (mqtt over tls) is required, the `options` object is passed through to [`tls.connect()`](http://nodejs.org/api/tls.html#tls_tls_connect_options_callback). If using a **self-signed certificate**, set `rejectUnauthorized: false`. However, be cautious as this exposes you to potential man in the middle attacks and isn't recommended for production.
471473

src/lib/client.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ const defaultConnectOptions: IClientOptions = {
6464
timerVariant: 'auto',
6565
}
6666

67-
export type MqttProtocol =
67+
export type BaseMqttProtocol =
6868
| 'wss'
6969
| 'ws'
7070
| 'mqtt'
@@ -76,6 +76,11 @@ export type MqttProtocol =
7676
| 'ali'
7777
| 'alis'
7878

79+
// create a type that allows all MqttProtocol + `+unix` string
80+
export type MqttProtocolWithUnix = `${BaseMqttProtocol}+unix`
81+
82+
export type MqttProtocol = BaseMqttProtocol | MqttProtocolWithUnix
83+
7984
export type StorePutCallback = () => void
8085

8186
export interface ISecureClientOptions {
@@ -142,7 +147,9 @@ export interface IClientOptions extends ISecureClientOptions {
142147
host?: string
143148
/** @deprecated use `host instead */
144149
hostname?: string
145-
/** Websocket `path` added as suffix */
150+
/** Set to true if the connection is to a unix socket */
151+
unixSocket?: boolean
152+
/** Websocket `path` added as suffix or Unix socket path when `unixSocket` option is true */
146153
path?: string
147154
/** The `MqttProtocol` to use */
148155
protocol?: MqttProtocol

src/lib/connect/index.ts

+33-15
Original file line numberDiff line numberDiff line change
@@ -71,31 +71,46 @@ function connect(
7171

7272
opts = opts || {}
7373

74+
// try to parse the broker url
7475
if (brokerUrl && typeof brokerUrl === 'string') {
7576
// eslint-disable-next-line
76-
const parsed = url.parse(brokerUrl, true)
77-
if (parsed.port != null) {
77+
const parsedUrl = url.parse(brokerUrl, true)
78+
const parsedOptions: Partial<IClientOptions> = {}
79+
80+
if (parsedUrl.port != null) {
7881
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
7982
// @ts-ignore
80-
parsed.port = Number(parsed.port)
83+
parsedOptions.port = Number(parsedUrl.port)
8184
}
8285

83-
opts = {
84-
...{
85-
port: parsed.port,
86-
host: parsed.hostname,
87-
protocol: parsed.protocol,
88-
query: parsed.query,
89-
auth: parsed.auth,
90-
},
91-
...opts,
92-
} as IClientOptions
86+
parsedOptions.host = parsedUrl.hostname
87+
parsedOptions.query = parsedUrl.query as Record<string, string>
88+
parsedOptions.auth = parsedUrl.auth
89+
parsedOptions.protocol = parsedUrl.protocol as MqttProtocol
90+
parsedOptions.path = parsedUrl.path
91+
92+
parsedOptions.protocol = parsedOptions.protocol?.replace(
93+
/:$/,
94+
'',
95+
) as MqttProtocol
9396

94-
if (opts.protocol === null) {
97+
opts = { ...parsedOptions, ...opts }
98+
99+
// when parsing an url expect the protocol to be set
100+
if (!opts.protocol) {
95101
throw new Error('Missing protocol')
96102
}
103+
}
104+
105+
opts.unixSocket = opts.unixSocket || opts.protocol?.includes('+unix')
97106

98-
opts.protocol = opts.protocol.replace(/:$/, '') as MqttProtocol
107+
if (opts.unixSocket) {
108+
opts.protocol = opts.protocol.replace('+unix', '') as MqttProtocol
109+
} else if (!opts.protocol?.startsWith('ws')) {
110+
// consider path only with ws protocol or unix socket
111+
// url.parse could return path (for example when url ends with a `/`)
112+
// that could break the connection. See https://github.com/mqttjs/MQTT.js/pull/1874
113+
delete opts.path
99114
}
100115

101116
// merge in the auth options if supplied
@@ -136,6 +151,9 @@ function connect(
136151

137152
if (!protocols[opts.protocol]) {
138153
const isSecure = ['mqtts', 'wss'].indexOf(opts.protocol) !== -1
154+
// returns the first available protocol based on available protocols (that depends on environment)
155+
// if no protocol is specified this will return mqtt on node and ws on browser
156+
// if secure it will return mqtts on node and wss on browser
139157
opts.protocol = [
140158
'mqtt',
141159
'mqtts',

test/mqtt.ts

+21
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,26 @@ describe('mqtt', () => {
3131
c.should.be.instanceOf(mqtt.MqttClient)
3232
c.options.should.have.property('username', 'user')
3333
c.options.should.have.property('password', 'pass')
34+
c.options.should.not.have.property('path')
35+
c.end((err) => done(err))
36+
})
37+
38+
it('should return an MqttClient with path set when protocol is ws/wss', function _test(t, done) {
39+
const c = mqtt.connect('ws://localhost:1883/mqtt')
40+
41+
c.should.be.instanceOf(mqtt.MqttClient)
42+
c.options.should.have.property('path', '/mqtt')
43+
c.options.should.have.property('unixSocket', false)
44+
c.end((err) => done(err))
45+
})
46+
47+
it('should work with unix sockets', function _test(t, done) {
48+
const c = mqtt.connect('mqtt+unix:///tmp/mqtt.sock')
49+
50+
c.should.be.instanceOf(mqtt.MqttClient)
51+
c.options.should.have.property('path', '/tmp/mqtt.sock')
52+
c.options.should.have.property('unixSocket', true)
53+
3454
c.end((err) => done(err))
3555
})
3656

@@ -47,6 +67,7 @@ describe('mqtt', () => {
4767
const c = mqtt.connect('mqtt://user@localhost:1883')
4868

4969
c.should.be.instanceOf(mqtt.MqttClient)
70+
c.options.should.not.have.property('path')
5071
c.options.should.have.property('username', 'user')
5172
c.end((err) => done(err))
5273
})

0 commit comments

Comments
 (0)