azure-sdk-for-js
azure-sdk-for-js copied to clipboard
[@azure/service-bus] Websocket connection failed with react native polyfills
- 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.
Label prediction was below confidence level 0.6 for Model:ServiceLabels: 'Service Bus:0.46679074,Azure.Core:0.23571692,Storage:0.0883106'
@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 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.
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.
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.
Or worst case, we could manually resolve ws module to Websocket as in
// 'ws' module polyfill
module.exports = WebSocket
@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.
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 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,
},
});
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 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.
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 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.
Amazing! Looking forward!
@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.