Skip to content

Commit

Permalink
feat: add unixSocket option and +unix suffix support to protocol (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
robertsLando committed May 28, 2024
1 parent 7d5820c commit 1004c78
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 19 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ Hello mqtt

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)


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

The URL can be on the following protocols: 'mqtt', 'mqtts', 'tcp',
'tls', 'ws', 'wss', 'wxs', 'alis'. The URL can also be an object as returned by
'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.

The URL can also be an object as returned by
[`URL.parse()`](http://nodejs.org/api/url.html#url_url_parse_urlstr_parsequerystring_slashesdenotehost),
in that case the two objects are merged, i.e. you can pass a single
object with both the URL and the connect options.
Expand Down Expand Up @@ -466,6 +467,7 @@ The arguments are:
- `log`: custom log function. Default uses [debug](https://www.npmjs.com/package/debug) package.
- `manualConnect`: prevents the constructor to call `connect`. In this case after the `mqtt.connect` is called you should call `client.connect` manually.
- `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`
- `unixSocket`: if you want to connect to a unix socket, set this to true

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.

Expand Down
11 changes: 9 additions & 2 deletions src/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const defaultConnectOptions: IClientOptions = {
timerVariant: 'auto',
}

export type MqttProtocol =
export type BaseMqttProtocol =
| 'wss'
| 'ws'
| 'mqtt'
Expand All @@ -76,6 +76,11 @@ export type MqttProtocol =
| 'ali'
| 'alis'

// create a type that allows all MqttProtocol + `+unix` string
export type MqttProtocolWithUnix = `${BaseMqttProtocol}+unix`

export type MqttProtocol = BaseMqttProtocol | MqttProtocolWithUnix

export type StorePutCallback = () => void

export interface ISecureClientOptions {
Expand Down Expand Up @@ -142,7 +147,9 @@ export interface IClientOptions extends ISecureClientOptions {
host?: string
/** @deprecated use `host instead */
hostname?: string
/** Websocket `path` added as suffix */
/** Set to true if the connection is to a unix socket */
unixSocket?: boolean
/** Websocket `path` added as suffix or Unix socket path when `unixSocket` option is true */
path?: string
/** The `MqttProtocol` to use */
protocol?: MqttProtocol
Expand Down
48 changes: 33 additions & 15 deletions src/lib/connect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,31 +71,46 @@ function connect(

opts = opts || {}

// try to parse the broker url
if (brokerUrl && typeof brokerUrl === 'string') {
// eslint-disable-next-line
const parsed = url.parse(brokerUrl, true)
if (parsed.port != null) {
const parsedUrl = url.parse(brokerUrl, true)
const parsedOptions: Partial<IClientOptions> = {}

if (parsedUrl.port != null) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
parsed.port = Number(parsed.port)
parsedOptions.port = Number(parsedUrl.port)
}

opts = {
...{
port: parsed.port,
host: parsed.hostname,
protocol: parsed.protocol,
query: parsed.query,
auth: parsed.auth,
},
...opts,
} as IClientOptions
parsedOptions.host = parsedUrl.hostname
parsedOptions.query = parsedUrl.query as Record<string, string>
parsedOptions.auth = parsedUrl.auth
parsedOptions.protocol = parsedUrl.protocol as MqttProtocol
parsedOptions.path = parsedUrl.path

parsedOptions.protocol = parsedOptions.protocol?.replace(
/:$/,
'',
) as MqttProtocol

if (opts.protocol === null) {
opts = { ...parsedOptions, ...opts }

// when parsing an url expect the protocol to be set
if (!opts.protocol) {
throw new Error('Missing protocol')
}
}

opts.unixSocket = opts.unixSocket || opts.protocol?.includes('+unix')

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

// merge in the auth options if supplied
Expand Down Expand Up @@ -136,6 +151,9 @@ function connect(

if (!protocols[opts.protocol]) {
const isSecure = ['mqtts', 'wss'].indexOf(opts.protocol) !== -1
// returns the first available protocol based on available protocols (that depends on environment)
// if no protocol is specified this will return mqtt on node and ws on browser
// if secure it will return mqtts on node and wss on browser
opts.protocol = [
'mqtt',
'mqtts',
Expand Down
21 changes: 21 additions & 0 deletions test/mqtt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,26 @@ describe('mqtt', () => {
c.should.be.instanceOf(mqtt.MqttClient)
c.options.should.have.property('username', 'user')
c.options.should.have.property('password', 'pass')
c.options.should.not.have.property('path')
c.end((err) => done(err))
})

it('should return an MqttClient with path set when protocol is ws/wss', function _test(t, done) {
const c = mqtt.connect('ws://localhost:1883/mqtt')

c.should.be.instanceOf(mqtt.MqttClient)
c.options.should.have.property('path', '/mqtt')
c.options.should.have.property('unixSocket', false)
c.end((err) => done(err))
})

it('should work with unix sockets', function _test(t, done) {
const c = mqtt.connect('mqtt+unix:///tmp/mqtt.sock')

c.should.be.instanceOf(mqtt.MqttClient)
c.options.should.have.property('path', '/tmp/mqtt.sock')
c.options.should.have.property('unixSocket', true)

c.end((err) => done(err))
})

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

c.should.be.instanceOf(mqtt.MqttClient)
c.options.should.not.have.property('path')
c.options.should.have.property('username', 'user')
c.end((err) => done(err))
})
Expand Down

0 comments on commit 1004c78

Please sign in to comment.