From bbc318764e1b483964a99a66f9917c7f809d4bcc Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Fri, 24 May 2024 16:57:54 +0200 Subject: [PATCH 1/9] fix: consider path when protocol is ws/wss --- src/lib/connect/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/connect/index.ts b/src/lib/connect/index.ts index 9a8deee5f..2fed3072b 100644 --- a/src/lib/connect/index.ts +++ b/src/lib/connect/index.ts @@ -87,6 +87,9 @@ function connect( protocol: parsed.protocol, query: parsed.query, auth: parsed.auth, + path: parsed.protocol?.startsWith('ws') + ? parsed.path + : undefined, }, ...opts, } as IClientOptions From e6da804f513be64fd245f6640a7079325a009174 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Fri, 24 May 2024 17:01:29 +0200 Subject: [PATCH 2/9] fix: add test --- test/mqtt.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/mqtt.ts b/test/mqtt.ts index 392856316..e6604e595 100644 --- a/test/mqtt.ts +++ b/test/mqtt.ts @@ -31,6 +31,15 @@ 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.end((err) => done(err)) }) @@ -47,6 +56,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)) }) From 488a5c63c578579269d84d0a515aa66221422d1c Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 28 May 2024 09:30:05 +0200 Subject: [PATCH 3/9] feat: add `unixSocket` option and `+unix` suffix support to protocol --- README.md | 6 ++++-- src/lib/client.ts | 11 +++++++++-- src/lib/connect/index.ts | 38 ++++++++++++++++++++++++-------------- test/mqtt.ts | 11 +++++++++++ 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index d4d31f96d..d6724eea9 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 url. 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. @@ -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. diff --git a/src/lib/client.ts b/src/lib/client.ts index 192b1c1ed..8ea645f61 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -64,7 +64,7 @@ const defaultConnectOptions: IClientOptions = { timerVariant: 'auto', } -export type MqttProtocol = +export type BaseMqttProtocol = | 'wss' | 'ws' | 'mqtt' @@ -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 { @@ -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 diff --git a/src/lib/connect/index.ts b/src/lib/connect/index.ts index 2fed3072b..111fb9717 100644 --- a/src/lib/connect/index.ts +++ b/src/lib/connect/index.ts @@ -73,26 +73,25 @@ function connect( 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 = {} + + 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) } + parsedOptions.host = parsedUrl.hostname + parsedOptions.query = parsedUrl.query as Record + parsedOptions.auth = parsedUrl.auth + parsedOptions.protocol = parsedUrl.protocol as MqttProtocol + parsedOptions.path = parsedUrl.path + opts = { - ...{ - port: parsed.port, - host: parsed.hostname, - protocol: parsed.protocol, - query: parsed.query, - auth: parsed.auth, - path: parsed.protocol?.startsWith('ws') - ? parsed.path - : undefined, - }, + ...parsedOptions, ...opts, - } as IClientOptions + } if (opts.protocol === null) { throw new Error('Missing protocol') @@ -101,6 +100,17 @@ function connect( opts.protocol = opts.protocol.replace(/:$/, '') as MqttProtocol } + opts.unixSocket = opts.unixSocket || opts.protocol?.includes('+unix') + + if (opts.unixSocket) { + opts.protocol = opts.protocol.replace('+unix', '') as MqttProtocol + } + + // consider path only with ws protocol or unix socket, url parse could return empty path that could break the connection + if (!opts.unixSocket && !opts.protocol?.startsWith('ws')) { + delete opts.path + } + // merge in the auth options if supplied parseAuthOptions(opts) diff --git a/test/mqtt.ts b/test/mqtt.ts index e6604e595..c76f90873 100644 --- a/test/mqtt.ts +++ b/test/mqtt.ts @@ -40,6 +40,17 @@ describe('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)) }) From 04588adca8d8e9c7fb8601ddff0c6a47638ed75f Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 28 May 2024 09:32:17 +0200 Subject: [PATCH 4/9] fix: readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6724eea9..e82ca1b55 100644 --- a/README.md +++ b/README.md @@ -353,7 +353,7 @@ 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'. If you are trying to connect to a unix socket just append the `+unix` suffix to the url. This will set the `unixSocket` property automatically. +'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), From d729e5d4227483552bc28610668e76b965331e92 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 28 May 2024 09:39:47 +0200 Subject: [PATCH 5/9] fix: minor refactor --- src/lib/connect/index.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/lib/connect/index.ts b/src/lib/connect/index.ts index 111fb9717..0048dbb44 100644 --- a/src/lib/connect/index.ts +++ b/src/lib/connect/index.ts @@ -71,6 +71,7 @@ function connect( opts = opts || {} + // try to parse the broker url if (brokerUrl && typeof brokerUrl === 'string') { // eslint-disable-next-line const parsedUrl = url.parse(brokerUrl, true) @@ -93,21 +94,21 @@ function connect( ...opts, } - if (opts.protocol === null) { - throw new Error('Missing protocol') - } + opts.protocol = opts.protocol?.replace(/:$/, '') as MqttProtocol + } - opts.protocol = opts.protocol.replace(/:$/, '') as MqttProtocol + if (!opts.protocol) { + throw new Error('Missing protocol') } - opts.unixSocket = opts.unixSocket || opts.protocol?.includes('+unix') + opts.unixSocket = opts.unixSocket || opts.protocol.includes('+unix') if (opts.unixSocket) { opts.protocol = opts.protocol.replace('+unix', '') as MqttProtocol - } - - // consider path only with ws protocol or unix socket, url parse could return empty path that could break the connection - if (!opts.unixSocket && !opts.protocol?.startsWith('ws')) { + } 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 } From 8ded4bc6c4c344a3161b84857fb8fa59ecd447e1 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 28 May 2024 09:45:41 +0200 Subject: [PATCH 6/9] fix: validate protocol only when parsing url --- src/lib/connect/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/connect/index.ts b/src/lib/connect/index.ts index 0048dbb44..674c04a51 100644 --- a/src/lib/connect/index.ts +++ b/src/lib/connect/index.ts @@ -94,14 +94,14 @@ function connect( ...opts, } - opts.protocol = opts.protocol?.replace(/:$/, '') as MqttProtocol - } + if (!opts.protocol) { + throw new Error('Missing protocol') + } - if (!opts.protocol) { - throw new Error('Missing protocol') + opts.protocol = opts.protocol.replace(/:$/, '') as MqttProtocol } - opts.unixSocket = opts.unixSocket || opts.protocol.includes('+unix') + opts.unixSocket = opts.unixSocket || opts.protocol?.includes('+unix') if (opts.unixSocket) { opts.protocol = opts.protocol.replace('+unix', '') as MqttProtocol From 2f1d7bb889fd32e60ffe86b22e4df7f4d4c374e8 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 28 May 2024 09:47:39 +0200 Subject: [PATCH 7/9] fix: use object.assign --- src/lib/connect/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/connect/index.ts b/src/lib/connect/index.ts index 674c04a51..a470d6e2f 100644 --- a/src/lib/connect/index.ts +++ b/src/lib/connect/index.ts @@ -89,16 +89,16 @@ function connect( parsedOptions.protocol = parsedUrl.protocol as MqttProtocol parsedOptions.path = parsedUrl.path - opts = { - ...parsedOptions, - ...opts, - } - - if (!opts.protocol) { + if (!parsedOptions.protocol) { throw new Error('Missing protocol') } - opts.protocol = opts.protocol.replace(/:$/, '') as MqttProtocol + parsedOptions.protocol = parsedOptions.protocol.replace( + /:$/, + '', + ) as MqttProtocol + + Object.assign(opts, parsedOptions) } opts.unixSocket = opts.unixSocket || opts.protocol?.includes('+unix') From 9e6c96ca719ca911dcaf2c73c0d4447efb1a169e Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 28 May 2024 09:55:50 +0200 Subject: [PATCH 8/9] fix: edge cases --- src/lib/connect/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/connect/index.ts b/src/lib/connect/index.ts index a470d6e2f..b0eafcae9 100644 --- a/src/lib/connect/index.ts +++ b/src/lib/connect/index.ts @@ -89,16 +89,16 @@ function connect( parsedOptions.protocol = parsedUrl.protocol as MqttProtocol parsedOptions.path = parsedUrl.path - if (!parsedOptions.protocol) { - throw new Error('Missing protocol') - } - - parsedOptions.protocol = parsedOptions.protocol.replace( + parsedOptions.protocol = parsedOptions.protocol?.replace( /:$/, '', ) as MqttProtocol - Object.assign(opts, parsedOptions) + opts = { ...parsedOptions, ...opts } + + if (!opts.protocol) { + throw new Error('Missing protocol') + } } opts.unixSocket = opts.unixSocket || opts.protocol?.includes('+unix') From abe0113ea9538202b350f1968282ee96520685d5 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 28 May 2024 10:13:50 +0200 Subject: [PATCH 9/9] style: add comments --- src/lib/connect/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/connect/index.ts b/src/lib/connect/index.ts index b0eafcae9..41dc94e22 100644 --- a/src/lib/connect/index.ts +++ b/src/lib/connect/index.ts @@ -96,6 +96,7 @@ function connect( opts = { ...parsedOptions, ...opts } + // when parsing an url expect the protocol to be set if (!opts.protocol) { throw new Error('Missing protocol') } @@ -150,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',