azure-sdk-for-js icon indicating copy to clipboard operation
azure-sdk-for-js copied to clipboard

[@azure/service-bus] Websocket connection failed with react native polyfills

Open proohit opened this issue 3 years ago • 12 comments

  • Package Name: @azure/service-bus
  • Package Version: 7.7.1
  • Operating system:
  • [ ] nodejs
    • version:
  • [ ] browser
    • name/version:
  • [x] react-native
    • name/version: 0.70.1
  • [x] typescript
    • version: 4.8.3
  • Is the bug related to documentation in
    • [x] README.md
    • [ ] source code documentation
    • [ ] SDK API docs on https://docs.microsoft.com

Describe the bug I'm trying to run the service-bus client on a react native client. As suggested by the @azure/service-bus readme, I installed polyfills for the node core modules. I tried two ways:

  • manually resolving used packages to polyfills
//babel.config.js
module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    [
      'module-resolver',
      {
        alias: {
          crypto: 'crypto-browserify',
          process: 'process/browser',
          os: 'os-browserify/browser',
          dns: '@i2labs/dns',
          stream: 'stream-browserify',
          buffer: 'buffer',
          path: 'path-browserify',
        },
      },
    ],
  ],
};
}
  • Shimming with rn-nodeify
rn-nodeify --install process,path,os,stream,buffer,crypto,net,tls,assert,dns,dgram --hack

Both attempts result in the following WebSocket error:

ServiceBusError: Websocket connection failed.
    at construct (native)
    at Wrapper (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:23518:64)
    at construct (native)
    at _createSuperInternal (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:188184:406)
    at call (native)
    at MessagingError (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:188733:27)
    at construct (native)
    at _createSuperInternal (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:112719:406)
    at call (native)
    at ServiceBusError (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:116955:29)
    at translateServiceBusError (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:116997:33)
    at ?anon_0_ (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:122197:59)
    at throw (native)
    at asyncGeneratorStep (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:24748:26)
    at _throw (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:24774:29)
    at tryCallOne (/Users/distiller/react-native/sdks/hermes/build_iphonesimulator/lib/InternalBytecode/InternalBytecode.js:53:16)
    at anonymous (/Users/distiller/react-native/sdks/hermes/build_iphonesimulator/lib/InternalBytecode/InternalBytecode.js:139:27)
    at apply (native)
    at anonymous (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:31334:26)
    at _callTimer (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:31234:17)
    at _callReactNativeMicrotasksPass (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:31269:17)
    at callReactNativeMicrotasks (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:31477:44)
    at __callReactNativeMicrotasks (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:2869:46)
    at anonymous (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:2647:45)
    at __guard (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:2852:15)
    at flushedQueue (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:2646:21)
    at callFunctionReturnFlushedQueue (http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&modulesOnly=false&runModule=true&app=...:2628:33)

React Native does bring Websocket natively, so I was assuming to have similar, if not the same environment as in browsers, thus using browser polyfills. I'm a little clueless at this point though.

Thanks for any help.


By the way: The list of used core modules to polyfill in the readme is not correct because the package seems to use more core modules, i.e. crypto, dns, stream. Might add those to the readme.

proohit avatar Oct 11 '22 15:10 proohit

Label prediction was below confidence level 0.6 for Model:ServiceLabels: 'Service Bus:0.46679074,Azure.Core:0.23571692,Storage:0.0883106'

azure-sdk avatar Oct 11 '22 15:10 azure-sdk

@proohit Thank you very much for investigating and reporting the issue! React-Native is not officially supported yet by our SDK. We have https://github.com/Azure/azure-sdk-for-js/issues/5771 adding the support. Currently a couple packages are working fine in the Android emulator after adding polyfills. The messaging SDKs (service-bus/event-hubs) are in my plan to look into next. Previously a known blocker is our underlying dependency rhea's react-native support.

jeremymeng avatar Oct 12 '22 16:10 jeremymeng

@jeremymeng Thank you for your efforts, that sounds promising!

When the readme says to support browsers, what exactly is missing in react native? JavaScript Core has a lot of functionality + babel polyfills to align to web standards, including fetch and WebSockets over TCP. As far as I understand, the service-bus client doesn't use any specific web APIs, thus it should work on react native as well.

I can't tell for sure but the polyfills I included should not be using any specific web APIs as well. Plus that's the whole idea behind rn-nodeify, even if it may be outdated today. At least it gets to the point where a connection via WebSockets is attempted.

proohit avatar Oct 12 '22 21:10 proohit

When the readme says to support browsers, what exactly is missing in react native?

I don't know the exact gap until I try it first. I haven't tested our service bus package on react-native. One example of issues we hit previously was that fetch api on react-native doesn't support stream.

You are right that service-bus client doesn't use any specific web APIs other than the websocket support. We pass some websocket options to the underlying library (rhea) and don't really manage the connection ourselves. Our library does allow overriding the websocket implementation via options: https://github.com/Azure/azure-sdk-for-js/blob/074b7d557f81d22fc89abaf271e7b59c18416a0e/sdk/core/core-amqp/src/ConnectionContextBase.ts#L203

We have a sample showing how to use the ws module's websocket implementation in order to set the proxy agent. Maybe worth a try to see if ws works in react-native? I will check it out too.

jeremymeng avatar Oct 12 '22 22:10 jeremymeng

Sounds good. Though I would suggest something like https://github.com/heineiuo/isomorphic-ws, because it supports NodeJs and browsers (via global.WebSocket), so in theory should work in RN too. I don't know if global.WebSocket is defined in react-native, but that fix should be as easy as global.WebSocket = WebSocket.

If the override in rhea works and the APIs match, that should work.

proohit avatar Oct 14 '22 12:10 proohit

Or worst case, we could manually resolve ws module to Websocket as in

// 'ws' module polyfill
module.exports = WebSocket

proohit avatar Oct 14 '22 12:10 proohit

@proohit I just tried using "ws" in react-native. Unfortunately, react-native packager seemed to prefer the browser version and gave me this error.

I looked at isomorphic-ws but it looks just a wrapper around "ws" and browser WebSocket and it doesn't support constructor options, which we allow users to pass in.

jeremymeng avatar Oct 14 '22 18:10 jeremymeng

Yes they don't wrap browser WebSocket, but instead suggest to evaluate by OR https://github.com/websockets/ws/issues/714#issuecomment-226181823 . So to speak default to a global WebSocket, if available.

proohit avatar Oct 14 '22 21:10 proohit

@proohit I found something during my investigation: react-native's WebSocket.binaryType by default is undefined, which is blocking rhea from behaving properly when running in browser. I will create an expo sample project where I got service-bus to send messages on Android Emulator. If you want, you can try and see whether this solves the connection issue you were seeing:

class WebSocketWrapper {
  constructor(...args) {
    const instance = new globalThis.WebSocket(...args);
    instance.binaryType = "blob";
    return instance;
  }
}

// then pass it via options

  const sbClient = new ServiceBusClient(connectionString, {
    webSocketOptions: {
      webSocket: WebSocketWrapper,
    },
  });

jeremymeng avatar Oct 14 '22 22:10 jeremymeng

This worked! Got some incompatibilities with react-native-tcp and react-native-udp, but those are not related to this issue. After fixing all those, communication to service-bus was successful. Though, I would wish for a cleaner solution, without polyfilling almost all of node modules and being dependent on many (also outdated) dependencies.

proohit avatar Oct 15 '22 11:10 proohit

@proohit glad to hear that you got it running! Curious did you test on real hardware too, or just emulator?

Yes, some nodejs modules are not really used when running in browsers. However, since in previous versions, they are all required by the commonjs version, react-native packager or bundler would report error if they are not found during build time. We just merged a PR to allow importing asynchronously #23524. With this, we may be able to reduce the number of polyfills needed.

jeremymeng avatar Oct 17 '22 17:10 jeremymeng

I tested it on the simulators, but it only worked on iOS due to some old bridge libraries on android. As I said this has nothing to do with azure sdk, but it would be great not to be dependent on polyfills as much. Your PR looks promising and I'm eager to find out which polyfills you want to include! Have you got some examples for the mappings?

proohit avatar Oct 17 '22 20:10 proohit

@proohit I just merged a PR to add a sample showing Event Hubs and Service Bus SDK usage in react-native. In this sample I used node-libs-react-native, which wraps a number of polyfills. I will probably try to reduce the set to the aboslutely required ones.

jeremymeng avatar Nov 17 '22 19:11 jeremymeng

Amazing! Looking forward!

proohit avatar Nov 17 '22 19:11 proohit

@proohit it's possible to reduce the polyfills required to five packages

  • buffer
  • path-browserify
  • process
  • os-browserfify
  • react-native-get-random-values

so the steps to add them are:

  • yarn add buffer os-browserify path-browserify process react-native-get-random-values
  • Modify metro.config.js
module.exports = {
  resolver: {
    extraNodeModules: {
      buffer: require.resolve('buffer/'),
      path: require.resolve('path-browserify'),
      process: require.resolve('process/browser.js'),
      os: require.resolve('os-browserify/browser.js'),
    },
  },
  // ...
}
  • App.tsx before importing Azure Service Bus SDK
global.Buffer = require('buffer').Buffer;
global.process = require('process');

import 'react-native-get-random-values';

I am closing this issue as the sample runs fine. Please let us know if you have further questions.

jeremymeng avatar Mar 06 '23 21:03 jeremymeng