Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add unixSocket option and +unix suffix support to protocol #1874

Merged
merged 9 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading