KSDaemon / wampy.js

Feature-rich lightweight WAMP (Web Application Messaging Protocol) Javascript implementation
https://ksdaemon.gitbook.io/wampy.js/
MIT License
290 stars 41 forks source link

Wampy not connecting in react-native env #107

Closed ph-teven closed 5 years ago

ph-teven commented 5 years ago

Describe the bug Wampy is not connecting properly in react-native environment.

The onConnect callback is not called.

Killing the crossbar service will trigger the onClose and onError callbacks. As mentioned also here: https://github.com/KSDaemon/wampy.js/issues/82#issuecomment-405012228

I also checked https://github.com/KSDaemon/wampy.js/issues/82 but could not find a working solution. Also the examples use w3cws as ws client.

I made a stripped down version of crossbar.io without authentication and bundled it into a repo to reproduce.

To Reproduce Clone https://github.com/zaytsevfuu/wampytest yarn install node node_modules/react-native/local-cli/cli.js run-android yarn run start cd crossbar && ./start_wamp.sh

Expected behavior onConnect callback is called

Screenshots Screenshot 2019-04-25 10 48 35

Screenshot 2019-04-25 10 52 38

Environment (please complete the following information):

ephro commented 5 years ago

I am having this same problem.

I think it may have to do with the serializer, but i'm not sure. When I looked at #82 for an example and added

serializer: new JsonSerializer(),

I get this error on crossbar 19.3.5:

"abort": false, "reason": "duplicate protocol 'wamp.2.json' specified in HTTP Sec-WebSocket-Protocol header", "log_time": 1557273573.8720865, "level": "warn", "namespace": "crossbar.router.protocol.WampWebSocketServerProtocol", "text": "dropping connection to peer tcp4:XXX.XXX.XXX.XXX:PPPPP with abort=False: duplicate protocol 'wamp.2.json' specified in HTTP Sec-WebSocket-Protocol header"

When i remove the serializer line it looks like the connection is made and I see the low level pings on the server, but I don't see any exchange. I don't see the onConnect fired, when I user wampcra I don't see the onChallenge fire either.

I've tried with Websocket and with w3cws as in the example in #82

ephro commented 5 years ago

So, I got it to work after a lot of debugging. I'm not 100% what is going wrong with Crossbar or with the library, but here is my fix for @zaytsevfuu

The problem I found is that serverProtocol on the line below is always undefined

https://github.com/KSDaemon/wampy.js/blob/0a962f2130d60a2a7faacf4d49700ab4fbb20878/src/wampy.js#L569

which causes this to always fail

https://github.com/KSDaemon/wampy.js/blob/0a962f2130d60a2a7faacf4d49700ab4fbb20878/src/wampy.js#L582

and it aborts the hello and joins.

My current hack to get the library working is to serverProtocol = 'json' on line 569

After that change it speaks to crossbar just fine. I'm pretty sure there is a deeper issue at heart here, but I don't understand this library well enough to create a pull.

KSDaemon commented 5 years ago

Hi all, guys! First of all, thanks for trying to figure it out. I'm not a react native expert. But it seems to me, that problem is lying somewhere on websocket communication level. As described in RFC 6455:

The client can request that the server use a specific subprotocol by including the |Sec-WebSocket-Protocol| field in its handshake. If it is specified, the server needs to include the same field and one of the selected subprotocol values in its response for the connection to be established.

Wampy sends Sec-WebSocket-Protocol: wamp.2.json. Or to be more correct, react native environment websocket object must send Sec-WebSocket-Protocol: wamp.2.json header. W3cws implementation works right. We need to inspect network communication between client and crossbar and figure out what is sending in both directions. I'll try to reproduce it.

KSDaemon commented 5 years ago

@zaytsevfuu Can you help me with starting up test you provided? First, you forget about installing android-sdk and missed some other steps:

brew cask install android-sdk
export ANDROID_HOME="/usr/local/share/android-sdk"
sdkmanager --licenses     # accept license agreements

After this i've got next errors:

kostik@GrayWolf:~/Projects/wampytest/ {master|…1}!> node node_modules/react-native/local-cli/cli.js run-android                                                   [11:35] cmd#6460
info JS server already running.
info Building and installing the app on the device (cd android && ./gradlew app:installDebug)...

> Configure project :app
Checking the license for package Android SDK Build-Tools 28.0.3 in /usr/local/share/android-sdk/licenses
License for package Android SDK Build-Tools 28.0.3 accepted.
Preparing "Install Android SDK Build-Tools 28.0.3 (revision: 28.0.3)".
"Install Android SDK Build-Tools 28.0.3 (revision: 28.0.3)" ready.
Installing Android SDK Build-Tools 28.0.3 in /usr/local/share/android-sdk/build-tools/28.0.3
"Install Android SDK Build-Tools 28.0.3 (revision: 28.0.3)" complete.
"Install Android SDK Build-Tools 28.0.3 (revision: 28.0.3)" finished.
Checking the license for package Android SDK Platform 28 in /usr/local/share/android-sdk/licenses
License for package Android SDK Platform 28 accepted.
Preparing "Install Android SDK Platform 28 (revision: 6)".
"Install Android SDK Platform 28 (revision: 6)" ready.
Installing Android SDK Platform 28 in /usr/local/share/android-sdk/platforms/android-28
"Install Android SDK Platform 28 (revision: 6)" complete.
"Install Android SDK Platform 28 (revision: 6)" finished.
Checking the license for package Android SDK Platform-Tools in /usr/local/share/android-sdk/licenses
License for package Android SDK Platform-Tools accepted.
Preparing "Install Android SDK Platform-Tools (revision: 28.0.3)".
"Install Android SDK Platform-Tools (revision: 28.0.3)" ready.
Installing Android SDK Platform-Tools in /usr/local/share/android-sdk/platform-tools
"Install Android SDK Platform-Tools (revision: 28.0.3)" complete.
"Install Android SDK Platform-Tools (revision: 28.0.3)" finished.
/Users/kostik/.gradle/caches/modules-2/files-2.1/com.squareup.okhttp3/okhttp/3.12.1/dc6d02e4e68514eff5631963e28ca7742ac69efe/okhttp-3.12.1.jar: D8: Type `org.conscrypt.Conscrypt` was not found, it is required for default or static interface methods desugaring of `java.security.Provider okhttp3.internal.platform.ConscryptPlatform.getProvider()`
[adb]: * daemon not running; starting now at tcp:5037
[adb]: * daemon started successfully

> Task :app:installDebug FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:installDebug'.
> com.android.builder.testing.api.DeviceException: No connected devices!

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1m 16s
26 actionable tasks: 26 executed
error Could not install the app on the device, read the error above for details.
Make sure you have an Android emulator running or a device connected and have
set up your Android development environment:
https://facebook.github.io/react-native/docs/getting-started.html
error Command failed: ./gradlew app:installDebug. Run CLI with --verbose flag for more details.

okhttp-3.12.1.jar: D8: Type org.conscrypt.Conscrypt was not found, it is required for default or static interface methods desugaring of java.security.Provider okhttp3.internal.platform.ConscryptPlatform.getProvider()

KSDaemon commented 5 years ago

@ephro Can you check what is returned from server after initializing ws connection. I mean this._ws.protocol. It must contain string wamp.2.json.

It works as expected with crossbar in node.js/browsers environments.

ph-teven commented 5 years ago

@ephro Wow amazing. Thanks for digging into this.

@KSDaemon Hm i have never seen this error before. I recommend opening the android directory with Android Studio. It is very good at fixing problems by installing missing sdks and build tools.

You can install the apk by running the project from Ancdroid Studio and then starten the JS bundler with npm run start

If you see a white screen on the emulator just re-run the project from Android studio.

KSDaemon commented 5 years ago

Okay, @zaytsevfuu thanks! I'll try.

ephro commented 5 years ago

@KSDaemon The error you are getting is because you don't have an android virtual device created or an android phone connected with USB debugging enabled.

If you have Android Studio installed, these are the instructions to making a device.

https://developer.android.com/studio/run/managing-avds

I'll dig into the other question a little later today and try to get some network dumps to go along with it.

ephro commented 5 years ago

Looking at the wireshark logs I see the app send

origin: http://XXXXXXXXXX:PPPP
Sec-WebSocket-Protocol: wamp.2.json
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: KKKKKKKKKKKKK
Sec-WebSocket-Version: 13
Host: XXXXXXXXXXXX:PPPP
Accept-Encoding: gzip
User-Agent: okhttp/3.12.1

and the server replies with

Server: Crossbar
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Protocol: wamp.2.json
Sec-WebSocket-Accept: KKKKKKKKKKKKK

I redacted the addresses and keys.

In the wampy.js library I added a console dump of this._ws right before the log line of [wampy] websocket connected and see the following:

image

So there is no part of _ws that is getting set. That's what I ran into before and put in the quick hack to just make the library speak json.

The image above is using ws: WebSocket in the constructor

When I use w3cws = require('websocket').w3cwebsocket like in some of your other examples and ws: w3cws I get the exact same as below

image

I'm also just using non-encrypted sockets right now as I haven't gotten to the point of getting SSL to work, which seems like a fairly large headache with android.

Let me know if there is anything else I can do to help.

KSDaemon commented 5 years ago

@ephro Oh, great! Can you paste here or via gist full dump of this._ws ? I'm trying to look into react-native sources and their ws implementation.  And i see only one line in all repository, where server answered Sec-WebSocket-Protocol is set up: https://github.com/facebook/react-native/blob/8491cc36dda18589b81d28fe6af220eaf1767b3a/Libraries/WebSocket/RCTSRWebSocket.m#L403-412 https://github.com/facebook/react-native/search?q=_protocol&unscoped_q=_protocol

And i can't see were it is exposed.

ephro commented 5 years ago

Here is this._ws using util.inspect since it has circular references

{ CONNECTING: 0,
  OPEN: 1,
  CLOSING: 2,
  CLOSED: 3,
  readyState: 1,
  _eventEmitter: { _subscriber: { _subscriptionsForType: [Object], _currentSubscription: null } },
  _socketId: 1,
  _subscriptions: 
   [ { subscriber: [Object],
       emitter: [Object],
       listener: [Function],
       context: undefined,
       eventType: 'websocketMessage',
       key: 1 },
     { subscriber: [Object],
       emitter: [Object],
       listener: [Function],
       context: undefined,
       eventType: 'websocketOpen',
       key: 1 },
     { subscriber: [Object],
       emitter: [Object],
       listener: [Function],
       context: undefined,
       eventType: 'websocketClosed',
       key: 1 },
     { subscriber: [Object],
       emitter: [Object],
       listener: [Function],
       context: undefined,
       eventType: 'websocketFailed',
       key: 1 } ] }

and with just a console.log

{CONNECTING: 0, OPEN: 1, CLOSING: 2, CLOSED: 3, readyState: 1, …}CLOSED: 3CLOSING: 2CONNECTING: 0OPEN: 1onclose: (...)onerror: (...)onmessage: (...)onopen: (...)readyState: 1_binaryType: "arraybuffer"_eventEmitter: NativeEventEmitter {_subscriber: EventSubscriptionVendor}_socketId: 1_subscriptions: (4) [EmitterSubscription, EmitterSubscription, EmitterSubscription, EmitterSubscription]binaryType: (...)Symbol(listeners): {open: {…}, close: {…}, message: {…}, error:

I'm not really sure why it's not showing everything i the first as it is in the second.

It's not as easy as one would hope to get the data out of react-native.

KSDaemon commented 5 years ago

btw, I've installed Android Studio and have created emulator device and run it — but no luck. Same if i try to run an example with node node_modules/react-native/local-cli/cli.js run-ios (other error)

Anyway, i think the problem lies in rn websocket implementation. Time to file a bug there. Here it is: https://github.com/facebook/react-native/issues/24796

Please, @zaytsevfuu @ephro Have a look there, so you can provide more info if required. Thanks!

ephro commented 5 years ago

@KSDaemon

Would you accept a change to the library where

https://github.com/KSDaemon/wampy.js/blob/0a962f2130d60a2a7faacf4d49700ab4fbb20878/src/wampy.js#L582

would be changed to

if (serverProtocol === 'json' || typeof this._ws.protocol === 'undefined') {

I know it's a little hacky, but it would fix our problems until the websocket library gets hopefully patched.

KSDaemon commented 5 years ago

@ephro I was thinking about your proposal... at first glance, i thought it is bad in some cases, but going deeper, i think this should not break the current workflow (i hope) Can we somehow detect, that we are running in RN env? If it's possible that we can make a hack just for this case. I've found this https://github.com/facebook/react-native/pull/2120 seems what is needed.

ephro commented 5 years ago

@KSDaemon I did some more digging and I think the problem is just with react-native and android because of the OkHttp code. I don't have mac handy to test on IOS right now, however this test should only check for the undefined protocol in RN envs, while not affecting other ones. The code below would replace 582 from the post above.

            if (serverProtocol === 'json'
                ||  (typeof navigator != 'undefined'
                    && navigator.product == 'ReactNative'
                    && typeof this._ws.protocol === 'undefined')) {

Apologies for the multi-line here, but I wanted to to fit in the post more easily.

KSDaemon commented 5 years ago

I'm thinking...

I'm afraid this solution won't work if you use any other serializer, besides json.

For example, if you initialize wampy with msgpack serializer, then connect to crossbar (which supports different serializers), crossbar will accept wamp.2.msgpack, but wampy still will not be able to distinguish this answer and will choose json.

Well, anyway, this is still better than the original code and it doesn't break current behavior. So, i'll implement this.

KSDaemon commented 5 years ago

I'll publish new release now, so you can test.

ephro commented 5 years ago

@KSDaemon I agree, it does break it if you use another serializer, but at least it connects. Without the protocols being exposed I'm not sure of any other way to handle it though. At least it connects with this change as ugly as it is.

I was trying to find out what the OkHttp android library does, but it's not well documented. I think it may do the negotiation for you, but i'm not really sure how to figure it out.

I will try to do some testing with the new release and sniff the packets to try to figure out what is happening and get back to you.

KSDaemon commented 5 years ago

@ephro great! You're welcome!

cabelitos commented 5 years ago

Just letting you guys know. I sent a fix for react-native. I hope it can help you guys. https://github.com/facebook/react-native/pull/25273

KSDaemon commented 5 years ago

@cabelitos Thats great! Looking forward for next release with ur MR included :)