From 103d1721d68952e536a3704a05a569c95f0a1987 Mon Sep 17 00:00:00 2001 From: Maxime Dirksen Date: Fri, 26 Jul 2024 08:42:39 +0200 Subject: [PATCH] feat: add `forceNativeWebSocket` client option (#1910) * Revert "fix(browser): add `navigator` polifilly for wechat mini (#1796)" This reverts commit c26908a242fa1f573689b03f554bb95d83e61c84. since the polyfill hardcode navigator, it is impossible to determine the userAgent at runtime * doc(README): update WeChat instructions * feat: add forceNativeWebSocket client option * Revert "feat: add compatibility with txiki.js (#1895)" This reverts commit 37b08c99fead5282e38b851ce1006f09521b038c. Not special support for txiki.js is required thanks to forceNativeWebSocket client option * style: unify import name IS_BROWSER -> isBrowser * fixup! feat: add forceNativeWebSocket client option style: fix lint * fixup! fixup! feat: add forceNativeWebSocket client option chore: remove test_store folder pushed refactor: load protocols only once refactor: use forceNativeWebSocket only for ws choice doc(README): typo + update forceNativeWebSocket behaviour description --- README.md | 15 +++++++------- esbuild.js | 10 +-------- src/lib/client.ts | 4 ++++ src/lib/connect/index.ts | 45 +++++++++++++++++++++++----------------- src/lib/connect/ws.ts | 6 +++--- src/lib/is-browser.ts | 8 +------ 6 files changed, 43 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index f13a9a98e..3012c2e2e 100644 --- a/README.md +++ b/README.md @@ -467,13 +467,13 @@ 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`. If none suits you, you can pass a timer object with set and clear properties: - ```js - timerVariant: { - set: (func, timer) => setInterval(func, timer), - clear: (id) => clearInterval(id) - } - ``` - + ```js + timerVariant: { + set: (func, timer) => setInterval(func, timer), + clear: (id) => clearInterval(id) + } + ``` + - `forceNativeWebSocket`: set to true if you're having detection issues (i.e. the `ws does not work in the browser` exception) to force the use of native WebSocket. It is important to note that if set to true for the first client created, then all the clients will use native WebSocket. And conversely, if not set or set to false, all will use the detection result. - `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. @@ -905,6 +905,7 @@ Supports [WeChat Mini Program](https://mp.weixin.qq.com/). Use the `wxs` protoco ```js import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only' // import before mqtt. +import 'esbuild-plugin-polyfill-node/polyfills/navigator' const mqtt = require("mqtt"); const client = mqtt.connect("wxs://test.mosquitto.org", { timerVariant: 'native' // more info ref issue: #1797 diff --git a/esbuild.js b/esbuild.js index c05b0bfb1..f157201d7 100644 --- a/esbuild.js +++ b/esbuild.js @@ -21,15 +21,7 @@ const options = { polyfillNode({ polyfills: [ 'readable-stream' - ], - globals: { - global: false, - __dirname: false, - __filename: false, - buffer: true, - process: true, - navigator: true, // Needed for WeChat, ref #1789 - } + ] }), { name: 'resolve-package-json', diff --git a/src/lib/client.ts b/src/lib/client.ts index da9597b7f..b9fa98480 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -282,6 +282,10 @@ export interface IClientOptions extends ISecureClientOptions { * or pass a custom timer object */ timerVariant?: TimerVariant | Timer + /** + * false, set to true to force the use of native WebSocket if you're having issues with the detection + */ + forceNativeWebSocket?: boolean } export interface IClientPublishOptions { diff --git a/src/lib/connect/index.ts b/src/lib/connect/index.ts index 3acfbdf59..a25863f78 100644 --- a/src/lib/connect/index.ts +++ b/src/lib/connect/index.ts @@ -6,7 +6,7 @@ import MqttClient, { MqttClientEventCallbacks, MqttProtocol, } from '../client' -import IS_BROWSER from '../is-browser' +import isBrowser from '../is-browser' import { StreamBuilder } from '../shared' // Handling the process.nextTick is not a function error in react-native applications. @@ -16,24 +16,7 @@ if (typeof process?.nextTick !== 'function') { const debug = _debug('mqttjs') -const protocols: Record = {} - -if (!IS_BROWSER) { - protocols.mqtt = require('./tcp').default - protocols.tcp = require('./tcp').default - protocols.ssl = require('./tls').default - protocols.tls = protocols.ssl - protocols.mqtts = require('./tls').default -} else { - protocols.wx = require('./wx').default - protocols.wxs = require('./wx').default - - protocols.ali = require('./ali').default - protocols.alis = require('./ali').default -} - -protocols.ws = require('./ws').default -protocols.wss = require('./ws').default +let protocols: Record = null /** * Parse the auth attribute and merge username and password in the options object. @@ -152,6 +135,30 @@ function connect( } } + // only loads the protocols once + if (!protocols) { + protocols = {} + if (!isBrowser && !opts.forceNativeWebSocket) { + protocols.ws = require('./ws').streamBuilder + protocols.wss = require('./ws').streamBuilder + + protocols.mqtt = require('./tcp').default + protocols.tcp = require('./tcp').default + protocols.ssl = require('./tls').default + protocols.tls = protocols.ssl + protocols.mqtts = require('./tls').default + } else { + protocols.ws = require('./ws').browserStreamBuilder + protocols.wss = require('./ws').browserStreamBuilder + + protocols.wx = require('./wx').default + protocols.wxs = require('./wx').default + + protocols.ali = require('./ali').default + protocols.alis = require('./ali').default + } + } + 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) diff --git a/src/lib/connect/ws.ts b/src/lib/connect/ws.ts index 1e8060c9e..dc72cae69 100644 --- a/src/lib/connect/ws.ts +++ b/src/lib/connect/ws.ts @@ -3,7 +3,7 @@ import { Buffer } from 'buffer' import Ws, { ClientOptions } from 'ws' import _debug from 'debug' import { DuplexOptions, Transform } from 'readable-stream' -import IS_BROWSER from '../is-browser' +import isBrowser from '../is-browser' import MqttClient, { IClientOptions } from '../client' import { BufferedDuplex, writev } from '../BufferedDuplex' @@ -44,7 +44,7 @@ function setDefaultOpts(opts: IClientOptions) { if (!opts.wsOptions) { options.wsOptions = {} } - if (!IS_BROWSER && opts.protocol === 'wss') { + if (!isBrowser && !opts.forceNativeWebSocket && opts.protocol === 'wss') { // Add cert/key/ca etc options WSS_OPTIONS.forEach((prop) => { if ( @@ -298,4 +298,4 @@ const browserStreamBuilder: StreamBuilder = (client, opts) => { return stream } -export default IS_BROWSER ? browserStreamBuilder : streamBuilder +export { browserStreamBuilder, streamBuilder } diff --git a/src/lib/is-browser.ts b/src/lib/is-browser.ts index d614d8be5..cdf926a3e 100644 --- a/src/lib/is-browser.ts +++ b/src/lib/is-browser.ts @@ -25,9 +25,6 @@ const isStandardBrowserEnv = () => { return false } -const isTxikijsEnv = () => - typeof navigator !== 'undefined' && navigator.userAgent === 'txiki.js' - const isWebWorkerEnv = () => Boolean( // eslint-disable-next-line no-restricted-globals @@ -40,10 +37,7 @@ const isReactNativeEnv = () => typeof navigator !== 'undefined' && navigator.product === 'ReactNative' const isBrowser = - isStandardBrowserEnv() || - isWebWorkerEnv() || - isReactNativeEnv() || - isTxikijsEnv() + isStandardBrowserEnv() || isWebWorkerEnv() || isReactNativeEnv() export const isWebWorker = isWebWorkerEnv()