Mqtt.js: Can't connect to websocket in Electron project

Created on 12 Feb 2020  路  4Comments  路  Source: mqttjs/MQTT.js

My url is ws://127.0.0.1:8083/mqtt. No problem in browser, but not in electron, and I don't see any error messages.

image

When I open the Developer Tool Console, I use it like this and can also connect

const mqtt = require('mqtt')
mqtt.connect('ws://127.0.0.1:8083/mqtt', opts)

image

using:

If you can help me, I will be very grateful!

browser

Most helpful comment

I think I stumbled on this too, I believe it's the way the package is designed, it utilizes different modules for browser/node contexts: https://github.com/mqttjs/MQTT.js/blob/66e295ada333eb47e0881c22c2ef6e65e852a72c/package.json#L60-L65

While this entry alone seems to pose no threat, the underlying library rewrites a whole module: https://github.com/maxogden/websocket-stream/blob/feeb372ff530621d6df85cb85d4bee03b879c54d/package.json#L46

And the default export mode is actually for node: https://github.com/mqttjs/MQTT.js/blob/66e295ada333eb47e0881c22c2ef6e65e852a72c/package.json#L22

Try setting a event listener for "close", because in my case, the connection got always interrupted and reconnected and no errors got reported, but the "close" handler fired.

As for the fix, I believe you are using require('mqtt') from Electron's Renderer context, same as I did. This library does not want to work that way and has various unexpected issues, I don't know exactly why.

I managed to fix the issue for now by specifically importing from mqtt/dist/mqtt.js, which is a pre-built browser-friendly module.


Some research inputs

  • The browser package.json field, is an instruction for bundlers/transpilers, not standard JS resolution algorithms: https://github.com/defunctzombie/package-browser-field-spec
  • Therefore, Electron, by default, seems to me, that will always use Node's module resolution algorithms and they most probably do not handle browser resolution in Renderer context.
  • For one to hit the browser part of the package, one should use a bundler/transpiler that supports it.

    • Which also is a normal use-case for Electron apps and webpack provides electron-renderer target, but then there might be another catch.

    • The electron-renderer target, I think, again tampers with resolution algorithms... knowing that Electron Renderer itself isn't a strict browser environment, they skip the browser field for resolution.

    • I am using webpack bundling for my Electron project, and the imports, while do not fail as in CodeSandbox (see below), still produce unfavourable results mentioned in this issue.

    • https://webpack.js.org/configuration/target/ seeing that electron-renderer uses NodeTargetPlugin for CommonJS modules (which MQTT.js is), will ignore the pacakge.json's browser rule.

  • Interestingly, though, the resolution thingies pose no static time errors, but at runtime the library goes wonky. My idea for that is that Electron, even in Renderer context, can import/execute Node-only stuff, but at the same time, it will only work if you're writing Renderer compliant code (there are some caveats). Apparently, this library does not do justice for the caveats and inside the Renderer it goes nuts.

    • I think that's the underlying websocket-stream library causing issues.

  • There is something wonky going on even in browsers when trying to import as module: https://codesandbox.io/s/goofy-oskar-vo0e4

    • This sandbox uses Parcel for compilation and Parcel knows how to handle the browser field: https://parceljs.org/module_resolution.html#package.json-%60browser%60-field

    • But this library itself and the underlying library websocket-stream library, currently, uses browser field only for transpilation purposes.

    • Therefore the resolution from ./mqtt.js to ./lib/connect/index.js doesn't help a bit when the package is published (it should point to compiled target ./dist/mqtt.js) because Parcel, seeing that the target module is a ES5 module, probably does not even try to transpile anything and therefore skips out of all the browser resolution steps and enters node mode of this package.


The fix

I think the involved packages should use the browser field properly, to point to bundled modules for usage in browser.

And that involves reworking the bundling process, probably utilizing actual webpack/browserify configs, instead of relying on browser field.


@YoDaMa I have never published packages to npm, but if you help me on that part (with a pre-release from a PR), I could open up the PR with an attempt to fix the issue.

All 4 comments

everything is perfectly normal in the browser but Electron.
I can connect to WebSocket broker when I downgrade to v1.14.1 but can not subscribe to any topic and don't see any error messages. if I change to v2 or v3 also cannot connect to WebSocket broker.

Using:

I think I stumbled on this too, I believe it's the way the package is designed, it utilizes different modules for browser/node contexts: https://github.com/mqttjs/MQTT.js/blob/66e295ada333eb47e0881c22c2ef6e65e852a72c/package.json#L60-L65

While this entry alone seems to pose no threat, the underlying library rewrites a whole module: https://github.com/maxogden/websocket-stream/blob/feeb372ff530621d6df85cb85d4bee03b879c54d/package.json#L46

And the default export mode is actually for node: https://github.com/mqttjs/MQTT.js/blob/66e295ada333eb47e0881c22c2ef6e65e852a72c/package.json#L22

Try setting a event listener for "close", because in my case, the connection got always interrupted and reconnected and no errors got reported, but the "close" handler fired.

As for the fix, I believe you are using require('mqtt') from Electron's Renderer context, same as I did. This library does not want to work that way and has various unexpected issues, I don't know exactly why.

I managed to fix the issue for now by specifically importing from mqtt/dist/mqtt.js, which is a pre-built browser-friendly module.


Some research inputs

  • The browser package.json field, is an instruction for bundlers/transpilers, not standard JS resolution algorithms: https://github.com/defunctzombie/package-browser-field-spec
  • Therefore, Electron, by default, seems to me, that will always use Node's module resolution algorithms and they most probably do not handle browser resolution in Renderer context.
  • For one to hit the browser part of the package, one should use a bundler/transpiler that supports it.

    • Which also is a normal use-case for Electron apps and webpack provides electron-renderer target, but then there might be another catch.

    • The electron-renderer target, I think, again tampers with resolution algorithms... knowing that Electron Renderer itself isn't a strict browser environment, they skip the browser field for resolution.

    • I am using webpack bundling for my Electron project, and the imports, while do not fail as in CodeSandbox (see below), still produce unfavourable results mentioned in this issue.

    • https://webpack.js.org/configuration/target/ seeing that electron-renderer uses NodeTargetPlugin for CommonJS modules (which MQTT.js is), will ignore the pacakge.json's browser rule.

  • Interestingly, though, the resolution thingies pose no static time errors, but at runtime the library goes wonky. My idea for that is that Electron, even in Renderer context, can import/execute Node-only stuff, but at the same time, it will only work if you're writing Renderer compliant code (there are some caveats). Apparently, this library does not do justice for the caveats and inside the Renderer it goes nuts.

    • I think that's the underlying websocket-stream library causing issues.

  • There is something wonky going on even in browsers when trying to import as module: https://codesandbox.io/s/goofy-oskar-vo0e4

    • This sandbox uses Parcel for compilation and Parcel knows how to handle the browser field: https://parceljs.org/module_resolution.html#package.json-%60browser%60-field

    • But this library itself and the underlying library websocket-stream library, currently, uses browser field only for transpilation purposes.

    • Therefore the resolution from ./mqtt.js to ./lib/connect/index.js doesn't help a bit when the package is published (it should point to compiled target ./dist/mqtt.js) because Parcel, seeing that the target module is a ES5 module, probably does not even try to transpile anything and therefore skips out of all the browser resolution steps and enters node mode of this package.


The fix

I think the involved packages should use the browser field properly, to point to bundled modules for usage in browser.

And that involves reworking the bundling process, probably utilizing actual webpack/browserify configs, instead of relying on browser field.


@YoDaMa I have never published packages to npm, but if you help me on that part (with a pre-release from a PR), I could open up the PR with an attempt to fix the issue.

@joltmode I think you are right. I solved this problem temporarily with this method.

webpack.config.js

pluginOptions: {
    electronBuilder: {
      // Prevent bundling of certain imported packages and instead retrieve these external dependencies at runtime.
      // In order to connect to websocket.
      externals: ['mqtt'],
    }
  }

connect.ts

const isWS: boolean = this.connectUrl.startsWith('ws') || this.connectUrl.startsWith('wss')
let mqtt = null
if (isWS) {
  mqtt = require('mqtt/dist/mqtt')
} else {
  mqtt = require('mqtt')
}

For anyone else that runs into this issue. I ended up working around this with the help of @tomsseisums comments, but instead of importing mqtt/dist/mqtt.js directly at the call site (i.e. import mqtt from 'mqtt/dist/mqtt.js';) I created a webpack alias as part of my electron renderer build:

resolve: {
  alias: {
    mqtt: resolve(__dirname, '../../node_modules/mqtt/dist/mqtt.js'),
  },
},

By using an alias the same code can function in both node as electron-renderer.

Was this page helpful?
5 / 5 - 1 ratings

Related issues

renerlemes picture renerlemes  路  5Comments

jingzhaoou picture jingzhaoou  路  4Comments

AlirezaMoseni picture AlirezaMoseni  路  4Comments

DavidMG01 picture DavidMG01  路  4Comments

markcnunes picture markcnunes  路  7Comments