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

React-Native support #573

Closed
franleplant opened this issue Mar 23, 2017 · 77 comments
Closed

React-Native support #573

franleplant opened this issue Mar 23, 2017 · 77 comments

Comments

@franleplant
Copy link

Hi There!

Thanks a lot for your work on this amazing library.

I've been fighting a lot with the browser builds, for several reasons:

  • Im using React Native and I need to manually create a dist file
  • Im using Typescript and I cannot include that dist file in my source because Typescript will complain no end
  • I tried other libraries and frankly this one is the best one
  • Playing around with node_modules is no good, there's no easy solution for automating steps inside that directory and it's also a transient directory, I don't want to get too attached to it in the case I need to rm -rf node_modules, which is too often

So, that's my problem, and here is my solution, which aims to be as simple as it gets

  • Add dist/mqtt.js to the source files, so I can easily require(mqtt/dist/mqtt). Names can be discussed but the important thing is to have that file available

There are other variations of this solution but I think this works and it's super simple and you already got the script to generate those dist files, the only remaining thing to do would be to un-ignore dist and to fix all the pre-commit checks that fail on those dist files

What do you think?

There might be more fancy solutions and Im open to anything but this requires minimal work and provides maximum satisfaction

@franleplant
Copy link
Author

Other easy solution is to add a postinstall hook that creates the build every time someone installs the package

@mcollina
Copy link
Member

Hey @franleplant, I understood your solution but I did not understand the problem you are facing.

You can easily do require('mqtt/dist/mqtt'), it should work. It's inside the package in the lastest versions, so there should be no problem.

@franleplant
Copy link
Author

Wow, apparently I'm blind, I need 👓 . I did found however this in the gitignore https://github.com/mqttjs/MQTT.js/blob/master/.gitignore#L10 which might have confused me.

So, two more things before closing

  • It'd be nice to have this stated in the docs instead of stating that the users need to manually build the browser package (I can help)
  • Im getting this error while using the dist/mqtt
Unable to resolve module `./store` from `Users/flp/code/MyProject/node_modules/mqtt/dist/mqtt.js`

This is the way Im importing it

const mqtt = require('mqtt/dist/mqtt')

The mqtt dist are made with browserify and my project uses webpack and react native, there might be some incompatibilities with the module systems?

Thanks a lot for your help

@mcollina
Copy link
Member

@franleplant that is highly possible. You probably have to have your webpack build it together with your application

@franleplant
Copy link
Author

So, after all, Im not using webpack, React Native uses it's own build system (analogue to Webpack and Browserify): Packager

Some more relevant information about other's peoples problems: facebook/react-native#1871

Potential solution I've tested is ReactNativify but in the end it didn't work with mqtt lib.

TLDR
Since React-Native uses Packager, a lot of hybrid node.js packages that depend on node.js modules or their shims via browserify/webpack do not work on React Native.

@keyeh
Copy link

keyeh commented Mar 31, 2017

@franleplant In the end what was your solution for MQTT on React Native?

@franleplant
Copy link
Author

hey @keyeh so after a lot of battling I ended up using https://github.com/rh389/react-native-paho-mqtt

It's rather immature and I've already had submit a patch but it's all that we got. Kudos to the author though, very responsive and nice to work with.

I'd love to use mqtt.js but I didn't find a way to easily make it work with React Native Packager.
Also the lib needs to accept WebSocket and LocalStorage implementations as parameters, since the React Native environment is different from the Browser, so things such as localStorage don't exist.

Paho let's you use whatever store that implements an interface as a replacement for LocalStorage.

I hope this helps.

@RangerMauve
Copy link
Contributor

RangerMauve commented Mar 31, 2017

I'm going to be working on React-Native support within the next two weeks, I had an initial attempt that let me compile the module successfully, but it resulted in runtime errors. I'll post here when I have progress.

@franleplant
Copy link
Author

@RangerMauve let me know if there is something I can take a look, give you feedback, give you PRs, because Im right now working on an RN App that uses mqtt, so I can live test it.

@RangerMauve
Copy link
Contributor

@franleplant Thanks for the offer! Here's the PR I did to get the ball rolling and discussion around it: nodejs/readable-stream#258

The original problem was that readable-stream was trying to require the stream module which was causing RN's bundler to freak out because it didn't see that module anywhere in its dependency graph (since it's a node thing).

I added a fix to explicitly exclude the stream module for RN, but now the issue is that excluding it still returns an empty object, which will cause util.inherits to freak out.

My idea (when I get around it it, or somebody else does), is to add a check so that if Stream is required properly, we should add a check to see if it actually has some stream-related property defined before using it.

Something like

var Stream;
(function () {
  try {
    Stream = require('st' + 'ream');
  } catch (_) {} finally {
    if (!Stream) Stream = require('events').EventEmitter;
  }
  // This is the addiition
  if(!Stream.on) Stream =  require('events').EventEmitter;
})();

@RangerMauve
Copy link
Contributor

You can try modifying the file in your node_modules folder to see if it works and if there's other issues that pop up.

@franleplant
Copy link
Author

Cool, Im going to investigate a bit next week, if I can make my self some time.

Thanks a lot for your participation!

@RangerMauve
Copy link
Contributor

Well, I'm going to get it to work by the end of April for sure since it's relevant to my day job. We've just been focusing on a react-native-web version first, so I hadn't gotten around it to it yet. :P

@franleplant
Copy link
Author

franleplant commented Mar 31, 2017

Sorry for using this thread as a forum, feel free to kick me out but this info is interesting for mqtt and react native and the community and does not require any effort from the mqtt.js maintainers.

So, I tried to make mqtt.js work with React native and went through a bunch of hoops but I think I made some progress, Im stuck in a place right now which if I understand how to get out I will update this post with it, but perhaps someone can help me with this.

These are the steps that I took to get to the point where I am now.

npm install url events path buffer assert
  • 4 Shim native dependencies with browserify variants

In your package.json add these to your deps

"fs": "git://github.com/mafintosh/browserify-fs.git#06414a74bc0e1f3a92f1891ae27f5594692967b8",
"os": "git://github.com/CoderPuppy/os-browserify.git#af8f17481c8097e679ea24700c6bf18d497ba3a1",

These point to today's master's top of each repo, you can update the commit hash accordingly.

This step basically says to your dependency tree (i.e. mqtt.js dependencies) that when doing
require('fs') use instead this require('browserify-fs'), it is pretty dirty but requires minimal effort

  • 5 Add Globals to your index.{ios,android}.js

Make sure it looks something like this

import React from 'react';
import { AppRegistry } from 'react-native';

// Define globally required stuff
GLOBAL.Buffer = require('buffer').Buffer;

// Some dependencies require process to be defined, so we comply with that
GLOBAL.process = {
  browser: true,
  env: {
    NODE_ENV: __DEV__ ? 'development' : 'production',
  }
}



// Make sure to use require instead of import syntax since the former
// will respect the order of things
const App = require('./app');

AppRegistry.registerComponent('MyApp', () => App);
  • 6 Stuck

Get stuck with this error:

Cannot read property 'prototype' of undefined

Which basically refers to this file

node_modules/websocket-stream/server.js

var inherits = require('inherits')

// Server is undefined, and that is the problem
var WebSocketServer = require('ws').Server
var stream = require('./stream')

module.exports = Server

function Server(opts, cb) {
  if (!(this instanceof Server)) {
    return new Server(opts, cb)
  }

  WebSocketServer.call(this, opts)

  var proxied = false
  this.on('newListener', function(event) {
    if (!proxied && event === 'stream') {
      proxied = true
      this.on('connection', function(conn) {
        this.emit('stream', stream(conn, opts))
      })
    }
  })

  if (cb) {
    this.on('stream', cb)
  }
}

// In here, inherits tries to access WebSocketServer.prototype to
// do the inheritance but WebSocketServer is undefined
inherits(Server, WebSocketServer)

Im going to try to keep bashing at this, but if anyone has some insights they will be highly appreciated.

@RangerMauve probably this could be a very simple approach for resolving the mqtt.js in React Native problem

@RangerMauve
Copy link
Contributor

RangerMauve commented Mar 31, 2017

That error is kind of weird. It shouldn't be requiring that file and instead should be getting stream.js in websocket-stream directly. What OS are you developing on?

Also, are you using the latest version of MQTT.js?

Also, what version of React-Native are you using for bundling?

@franleplant
Copy link
Author

franleplant commented Mar 31, 2017

OSX

The file looks pretty unconditional to the operative system or anything for that matter

@RangerMauve RangerMauve changed the title Browser dist files React-Native support Mar 31, 2017
@RangerMauve
Copy link
Contributor

Are you 100% sure that the error is within that file and not in here? Can you post a stack trace?

@RangerMauve RangerMauve reopened this Mar 31, 2017
@franleplant
Copy link
Author

100% sure because Im not doing anything magical, Im just following the stack trace. The first item is the inherits function def, and then it's that pointed file.

screen shot 2017-03-31 at 4 20 28 pm

Make sure you follow my steps to see in you can replicate this problem. The problem with the stream might have been solved by steps 3 or 5.

@RangerMauve
Copy link
Contributor

RangerMauve commented Mar 31, 2017

What version of MQTT.js and websocket-stream are you seeing in your package.json?

That file shouldn't even be imported because websocket stream specifies a browser field in it's package.json which should prevent it from loading server.js and instead only load stream.js.

It could be that either websocket-stream or packager are out of date if that alias isn't being effected.

Edit: Not package.json, node_modules folder

@franleplant
Copy link
Author

franleplant commented Mar 31, 2017

mqtt@^2.5.0:
  resolved "https://registry.yarnpkg.com/mqtt/-/mqtt-2.5.0.tgz#399078217ea361ae923d8de407c981ab8290968e"

websocket-stream@^3.3.3:
  resolved "https://registry.yarnpkg.com/websocket-stream/-/websocket-stream-3.3.3.tgz#361da5404a337e60cfbc29b4a46368762679df0b"

Greped from my yarn lock

That's weird, Im going to try to update my react native cli

Also, I think I saw that one of the problems with packager is that it doesnt recursively apply the "browser" field resolve, I need to find the issue where they disscuss about it though

@franleplant
Copy link
Author

My react native version is the latest:

react-native --version
react-native-cli: 2.0.1
react-native: 0.42.2

I would recommend to follow my steps to see where you get to

@RangerMauve
Copy link
Contributor

RangerMauve commented Mar 31, 2017

Hmm, sadly I don't have time today since I've gotta work on other stuff, but as I said, I'll be getting to it in about a week.

In the meantime, there's definitely something fishy happening with the packager not respecting the browser field. Maybe also look into updating your react-native version of the project to something a little newer.

AFAIK even though the CLI is installed globally, the scripts defined in your project for npm run start should be using the local version of the CLI in node_modules. When I was mucking about with the packager for a different reason, I had to modify it in my project rather than globally. I may be wrong, though, depending on how you're running your project.

@alexleonescalera
Copy link

alexleonescalera commented Apr 11, 2017

I've been dealing with the "property of undefined" issue for the last week.

My findings on this is that it is a problem with React Native's packager not using correctly the browser field in package.json.

This only happens when you are trying to override the main file in your package.json. To give an example:

"main": "mqtt.js",
"browser": {
  "./mqtt.js": "./lib/connect/index.js"
}

works fine with webpack, but with React Native, mqtt.js is not overriden correctly by lib/connect/index.js.

I made a test where you would create an extra hop to avoid using the main file in the browser field. That would be:

index.js

module.exports = require('./mqtt');

package.json

"main": "index.js",
"browser": {
 "./mqtt.js": "./lib/connect/index.js"
}

This worked for me, but it is just a hack. The real problem seems to be the packager on React Native is not correctly replacing mqtt.js for client since it is also the package.json's main file.

@RangerMauve
Copy link
Contributor

@alexleonescalera Good find! You should open an issue in their repo so that somebody looks into it. :D

@franleplant
Copy link
Author

good work @alexleonescalera, just to double check, by adding this "./mqtt.js": "./lib/connect/index.js" to the React Native app package.json you can use mqtt.js without further problems?

@angelos3lex
Copy link

angelos3lex commented Oct 29, 2018

@RangerMauve Can we use this solution (or other) to use mqtt://rabbitmqBroker to send messages to RabbitMQ broker, through react-native app? Or it only works with use of websockets(ws://rabbitmqBroker)?

@RangerMauve
Copy link
Contributor

@angelos3lex I haven't used a non-websocket broker with this technique yet, so I can't say for sure, but you might be able to get it to work by using the react-native-tcp library and aliasing net to react-native-tcp to have mqtt.js use it when it loads.

Let me know if you get it to work or have any trouble setting it up.

@qdsang
Copy link

qdsang commented Feb 26, 2019

install

npm install react-native-tcp --save
react-native link react-native-tcp
npm i stream-browserify stream -g

npm install buffer
npm install url events path buffer assert
npm install mqtt --save

edit node_modules/mqtt/package.json

  "browser": {
    "./mqtt.js": "./lib/connect/index.js",
    "fs": false,
    "tls": false,
    "net": "react-native-tcp"
  },

app.js

global.Buffer = global.Buffer || require('buffer').Buffer;
global.process.version = '';

var mqtt = require('mqtt')
var client  = mqtt.connect('mqtt://test.mosquitto.org')

client.on('connect', function () {
  client.subscribe('presence', function (err) {
    if (!err) {
      client.publish('presence', 'Hello mqtt')
    }
  })
})

client.on('message', function (topic, message) {
  // message is Buffer
  console.log(message.toString())
  client.end()
})

@kinoko8587
Copy link

kinoko8587 commented Apr 22, 2019

install

npm install react-native-tcp --save
react-native link react-native-tcp
npm i stream-browserify stream -g

npm install buffer
npm install url events path buffer assert
npm install mqtt --save

edit node_modules/mqtt/package.json

  "browser": {
    "./mqtt.js": "./lib/connect/index.js",
    "fs": false,
    "tls": false,
    "net": "react-native-tcp"
  },

app.js

global.Buffer = global.Buffer || require('buffer').Buffer;
global.process.version = '';

var mqtt = require('mqtt')
var client  = mqtt.connect('mqtt://test.mosquitto.org')

client.on('connect', function () {
  client.subscribe('presence', function (err) {
    if (!err) {
      client.publish('presence', 'Hello mqtt')
    }
  })
})

client.on('message', function (topic, message) {
  // message is Buffer
  console.log(message.toString())
  client.end()
})

It works in Android ,
but I get "mqtt stream error:received bad response code from server 403" in ios

@RangerMauve
Copy link
Contributor

Try using Websockets instead of TCP

@RangerMauve
Copy link
Contributor

Also, rn-nodeify might help with getting modules to work properly.

@zrg-team
Copy link

i used rn-nodeify but got error
@RangerMauve

Error: Could not determine host. Specify host

Code

export const DEFAULT_MESSAGING_OPTION = {
  // Authentication
  // username: 'emqx',
  // password: 'emqx',
  keepalive: 100000,
  protocol: '�mqtt',
  port: 1883,
  clean: true
}

export const MESSAGING_URL = '�66.42.60.2'

socketInstance = mqtt.connect(MESSAGING_URL, options)
    socketInstance.on('connect', function () {
      console.show('Connected to', MESSAGING_URL)
      socketInstance.subscribe('presence', function (err) {
        if (!err) {
          socketInstance.publish('presence', 'Hello mqtt')
        }
      })
    })

shim.js

if (typeof __dirname === 'undefined') global.__dirname = '/'
if (typeof __filename === 'undefined') global.__filename = ''
if (typeof process === 'undefined') {
  global.process = require('process')
} else {
  const bProcess = require('process')
  for (var p in bProcess) {
    if (!(p in process)) {
      process[p] = bProcess[p]
    }
  }
}

process.browser = false
if (typeof Buffer === 'undefined') global.Buffer = require('buffer').Buffer

// global.location = global.location || { port: 80 }
const isDev = typeof __DEV__ === 'boolean' && __DEV__
process.env['NODE_ENV'] = isDev ? 'development' : 'production'
if (typeof localStorage !== 'undefined') {
  localStorage.debug = isDev ? '*' : ''
}

// If using the crypto shim, uncomment the following line to ensure
// crypto is loaded first, so it can populate global.crypto
// require('crypto')

script

node_modules/.bin/rn-nodeify --install url,process,buffer,events,util --hack --yarn

@RangerMauve
Copy link
Contributor

There seems to be a weird character in your strings, is that on purpose?

@zrg-team
Copy link

@RangerMauve Thanks! i fixed that issue. but Client doesn't connect and no error is emitted

@RangerMauve
Copy link
Contributor

You're sure you got the right host/port and that your broker supports websockets?

@zrg-team
Copy link

zrg-team commented Apr 30, 2019

@vasupol11
Copy link

@RangerMauve Thanks! i fixed that issue. but Client doesn't connect and no error is emitted

Exact same problem, anyone has an update?

@zrg-team
Copy link

zrg-team commented May 11, 2019

@vasupol11 @RangerMauve it worked with "react-native-mqtt-client" and "rn-nodeify"
run below script after "npm install"

node_modules/.bin/rn-nodeify --install stream,net,url,process,buffer,events,util --hack

Example:

import connect from 'react-native-mqtt-client'

export default async (clientId) => {
  // const clientId = await deviceInfo.getUniqueID()
  return eventChannel(emitter => {
    try {
      if (socketInstance !== null) {
        socketInstance.end()
        socketInstance = null
      }
      const options = {
        ...DEFAULT_MESSAGING_OPTION,
        clientId
      }
      console.show('MQTT Connect', MESSAGING_URL)
      socketInstance = connect(MESSAGING_URL, options)
      socketInstance.on('connect', () => {
        console.show('MQTT Connected', MESSAGING_URL)
        emitter({ type: MESSAGES.CONNECTED, url: MESSAGING_URL })
      })
      socketInstance.on('message', (topic, message) => {
        console.show(`Message Topic ${topic}`, topic, message.toString())
        try {
          const data = JSON.parse(message.toString())
          emitter({ type: MESSAGES.EVENT, topic, message: data, success: true })
          if (listeners[topic] && typeof listeners[topic] === 'function') {
            listeners[topic](data, topic)
          }
        } catch (err) {
        }
      })
    } catch (err) {
      console.problem(err)
    }
    return () => {
      if (socketInstance !== null) {
        socketInstance.end()
        socketInstance = null
      }
    }
  })
}

@RangerMauve
Copy link
Contributor

@zrg-team That's great to hear, thank you for sharing! :D

@taoqf
Copy link
Contributor

taoqf commented Jun 13, 2019

I'm afraid it doesn't work. @zrg-team @RangerMauve .

@taoqf
Copy link
Contributor

taoqf commented Jun 14, 2019

https://www.npmjs.com/package/@taoqf/react-native-mqtt
This will do, and easy to be used. Thanks you all.

@Iamshankhadeep
Copy link

Any update on this issue?

@SibiAkkash
Copy link

https://www.npmjs.com/package/@taoqf/react-native-mqtt
This will do, and easy to be used. Thanks you all.

This works. FINALLY 🎉🎉

@mateogianolio
Copy link

Another solution (for react-native apps with typescript):

// @types/mqtt.d.ts
declare module 'mqtt/dist/mqtt' {
  export * from 'mqtt';
}
// screen.ts
import mqtt from 'mqtt/dist/mqtt';

@conkyliu
Copy link

https://www.npmjs.com/package/@taoqf/react-native-mqtt
This will do, and easy to be used. Thanks you all.

This works. FINALLY 🎉🎉

I tried to use this to connect to ali mqtt, but it kept failing, but I upgraded mqttjs to the latest version, and the connection was normal if I directly referenced mqttjs.

@rrebase
Copy link

rrebase commented Jan 31, 2023

https://www.npmjs.com/package/@taoqf/react-native-mqtt

This worked well 🙌, but it's a bit behind the upstream version of mqtt at this point.

So I forked the latest mqtt version and added what seems to be the minimal amount of changes needed to have it running in React Native: #1574 (published as @rrebase/mqtt if one wants to try it out)

@Thura69
Copy link

Thura69 commented Jun 25, 2023

Hi Guys, I want to connect MQTT broker that will be on engin8 raspberry pi 4 mqtt://username:password@host:1883 with React Native. I tried Paho-mqtt and did not work out. Please Help me .

@robertsLando
Copy link
Member

Hi everyone, I started helping maintaining this library.
Anyone that wants to open a PR to fix this? I can give it a look and merge on the upstream.

@robertsLando
Copy link
Member

I have fixed Browser docs by adding webpack and vite setup. Check them out

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests