Code Monkey home page Code Monkey logo

javaphoenixclient's Introduction

JavaPhoenixClient

Maven Central Build Status codecov

JavaPhoenixClient is a Kotlin implementation of the phoenix.js client used to manage Phoenix channels.

Basic Usage

fun connectToChatRoom() {

    // Create the Socket
    val params = hashMapOf("token" to "abc123")
    val socket = Socket("http://localhost:4000/socket/websocket", params)

    // Listen to events on the Socket
    socket.logger = { Log.d("TAG", it) }
    socket.onOpen { Log.d("TAG", "Socket Opened") }
    socket.onClose { Log.d("TAG", "Socket Closed") }
    socket.onError { throwable, response -> Log.d(throwable, "TAG", "Socket Error ${response?.code}") }

    socket.connect()

    // Join channels and listen to events
    val chatroom = socket.channel("chatroom:general")
    chatroom.on("new_message") { message ->
        val payload = message.payload
        ...
    }

    chatroom.join()
            .receive("ok") { /* Joined the chatroom */ }
            .receive("error") { /* failed to join the chatroom */ }
}

If you need to provide dynamic parameters that can change between calls to connect(), then you can pass a closure to the constructor

// Create the Socket
var authToken = "abc"
val socket = Socket("http://localhost:4000/socket/websocket", { mapOf("token" to authToken) })

// Connect with query parameters "?token=abc"
socket.connect()


// later in time, connect with query parameters "?token=xyz"
authToken = "xyz"
socket.connect() // or internal reconnect logic kicks in

You can also inject your own OkHttp Client into the Socket to provide your own configuration

// Configure your own OkHttp Client
val client = OkHttpClient.Builder()
    .connectTimeout(1000, TimeUnit.MILLISECONDS)
    .build()

// Create Socket with your custom instances
val params = hashMapOf("token" to "abc123")
val socket = Socket("http://localhost:4000/socket/websocket",
    params,
    client)

By default, the client use GSON to encode and decode JSON. If you prefer to manage this yourself, you can provide custom encode/decode functions in the Socket constructor.

// Configure your own GSON instance
val gson = Gson.Builder().create()
val encoder: EncodeClosure = {
    // Encode a Map into JSON using your custom GSON instance or another JSON library
    // of your choice (Moshi, etc)
}
val decoder: DecodeClosure = {
    // Decode a JSON String into a `Message` object using your custom JSON library 
}

// Create Socket with your custom instances
val params = hashMapOf("token" to "abc123")
val socket = Socket("http://localhost:4000/socket/websocket",
    params,
    encoder,
    decoder)

Installation

JavaPhoenixClient is hosted on MavenCentral. You'll need to make sure you declare mavenCentral() as one of your repositories

repositories {
    mavenCentral()
}

and then add the library. See releases for the latest version

dependencies {
    implementation 'com.github.dsrees:JavaPhoenixClient:1.3.0'
}

Feedback

Please submit in issue if you have any problems or questions! PRs are also welcome.

This library is built to mirror the phoenix.js and SwiftPhoenixClient libraries.

javaphoenixclient's People

Contributors

adamgrzybkowski avatar alonparker avatar alvarezariel avatar andrewrage avatar andrews-moc avatar ccolorcat avatar dpreussler avatar dsrees avatar dustinconrad avatar eyal-lezmy avatar ostap0207 avatar rawnsley avatar scottymack avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

javaphoenixclient's Issues

java.util.ConcurrentModificationException

I'm getting a lot of this on a recent library version. Could you look at this?

java.util.ConcurrentModificationException: 
  at java.util.ArrayList$Itr.next (ArrayList.java:860)
  at org.phoenixframework.Socket.onConnectionError$JavaPhoenixClient (Socket.java:511)
  at org.phoenixframework.Socket$connect$3.invoke (Socket.java:235)
  at org.phoenixframework.Socket$connect$3.invoke (Socket.java:61)
  at org.phoenixframework.WebSocketTransport.onFailure (WebSocketTransport.java:136)
  at okhttp3.internal.ws.RealWebSocket.failWebSocket (RealWebSocket.java:570)
  at okhttp3.internal.ws.RealWebSocket$1.onFailure (RealWebSocket.java:216)
  at okhttp3.RealCall$AsyncCall.execute (RealCall.java:180)
  at okhttp3.internal.NamedRunnable.run (NamedRunnable.java:32)
  at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1162)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:636)
  at java.lang.Thread.run (Thread.java:784)

Websocket is automatically reconnected event when intentionally closed

The last PR introduced a bug where the lib tries to reconnect even when the websocket is closed intentionally by calling disconnect()

This is what is happening:

  1. disconnect() is called
  2. onConnectionClosed() is invoked
  3. reconnectTimer is scheduled
  4. reconnectTimer connects to WebSocket again

I think we should somehow distinguish disconnect() called be the user and by the library itself. This would give us an option to know wether we should still want to try to reconnect or not.

@dsrees Could you look at this?

Issue with AGP v8.0.0 R8 minification

I am facing an issue after updating to the latest AGP v8 that after obfuscation (release build) I keep receiving phx_error even though the proxy confirms that I got okay from the server.

Any comments or thoughts about this issue?

I tried both 1.0.0 and 1.0.1 versions.

Decoding message payloads

Is this the best way to extract data from the Message.payload or am I missing a trick?

private fun onPhoenixOpen(m : Message) {
    val response = m.payload["response"] as LinkedTreeMap<*, *>
    val permissionsTokenEncoded = response.get("perms_token")
}

LinkedTreeMap is an implementation detail that's leaking out of JavaPhoenixClient and I would rather avoid it, especially if you plan to switch to the built-in serialization library in future.

Tries to reconnect with invalid token

If the connection is lost the library automatically tries to reconnect.
However, if one of the initial Socket params was an access token and the token expired in the meantime it will nevertheless be reused for the reconnect attempt. This leads to the server not accepting the reconnect requests.

We need some way to take over the reconnect strategy in the library client. Either a callback where we can pass new parameters or a way to disable the reconnect and handle it ourselves.

Missing proguard configuration in README.md

The library in the current state will not work when using proguard.

PhxMessage class member names are used to parse the response from WS.
The solution would be to add @SerializeName annotation, this way there is no need to add any additional proguard rules (apart from the ones for gson and okhhtp).

As for now you have to specify a rule like this
-keepclassmembernames class org.phoenixframework.PhxMessage { *; }

`unmatched topic` after reconnect

At first thank you for your work and this nice library! :-)

I have some trouble with reconnections. While my research I found the following issue on phoenix's JS client.

I get the same behavior on this client.

If I call disconnect and connect again to simulate a connection drop, I can not push messages anymore. I still get error with unmatched topic.

Message(joinRef=null, ref=16, topic=room:lobby, event=chan_reply_16, rawPayload={response={reason=unmatched topic}, status=error})

Debugging the client show all things are connected

canPush = true
isJoined = true
socket.isConnected = true

I have looked at the linked issue and the merge requests. But I don't understand the cause or the solution to fix this by myself.

In case this is really a bug and someone can help me to understand the cause and have some ideas how to fix this, I want to create a PR for this.

Release 1.1.3

Hi! When can we expect the release of version 1.1.3 to maven repository? The latest one is still 1.1.2 and there's a fix on 1.1.3 which I would be interested in

ConcurrentModificationException crash

Received this a couple of times. client version 0.2.5

Fatal Exception: java.util.ConcurrentModificationException
       at java.util.ArrayList$Itr.next(ArrayList.java:860)
       at org.phoenixframework.Socket.triggerChannelError(Socket.kt:467)
       at org.phoenixframework.Socket.onConnectionError$JavaPhoenixClient(Socket.kt:458)
       at org.phoenixframework.Socket$connect$3.invoke(Socket.kt:229)
       at org.phoenixframework.Socket$connect$3.invoke(Socket.kt:66)
       at org.phoenixframework.WebSocketTransport.onFailure(Transport.kt:134)
       at okhttp3.internal.ws.RealWebSocket.failWebSocket(RealWebSocket.java:571)
       at okhttp3.internal.ws.RealWebSocket$1.onFailure(RealWebSocket.java:221)
       at okhttp3.RealCall$AsyncCall.execute(RealCall.java:225)
       at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
       at java.lang.Thread.run(Thread.java:764)

Message.payload marked as non-nullable but can be null

Because Gson doesn't respect default values of parsed classes, if the payload is missing in the received event JSON then its value will still be null, breaking the non-nullability contract for the field (it is defined as val payload: Payload = Hashmap() in the Message class).

A quick fix would be to make the Payload field nullable.

Also, is there a reason why Message isn't a data class?

Leaking bindings when the channel reply times out?

In PhxPush.kt

/**
     * Starts the Timer which will trigger a timeout after a specific delay
     * in milliseconds is reached.
     */
    fun startTimeout() {
        this.timeoutTimer?.cancel()

        val ref = this.channel.socket.makeRef()
        this.ref = ref

        val refEvent = this.channel.replyEventName(ref)
        this.refEvent = refEvent

        // If a response is received  before the Timer triggers, cancel timer
        // and match the received event to it's corresponding hook.
        this.channel.on(refEvent) {
            this.cancelRefEvent()
            this.cancelTimeout()
            this.receivedMessage = it

            // Check if there is an event status available
            val message = it
            message.status?.let {
                this.matchReceive(it, message)
            }
        }

        // Start the timer. If the timer fires, then send a timeout event to the Push
        this.timeoutTimer = Timer()
        this.timeoutTimer?.schedule(timeout) {
            trigger("timeout", HashMap())
        }
    }

I think what is happenign is that if the timeout actually happens then a Message like PhxMessage(ref=chan_reply_8, topic=, event=, payload={status=timeout}, joinRef=null) is triggered. This is fine. However the bindings created with this.channel.on(refEvent) are never removed.

NPE in trigger()

Received this crash log. Only one instance of it so far, have not done any digging into what could have caused it. Reporting here for a reference

Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object kotlin.Triple.getFirst()' on a null object reference
       at org.phoenixframework.PhxChannel.trigger(PhxChannel.kt:337)
       at org.phoenixframework.PhxPush.trigger(PhxPush.kt:191)
       at org.phoenixframework.PhxPush$startTimeout$$inlined$schedule$1.run(Timer.kt:149)
       at java.util.TimerThread.mainLoop(Timer.java:555)
       at java.util.TimerThread.run(Timer.java:505)

java.lang.NoClassDefFoundError: org.phoenixframework.PhxSocket for api<24

Hello! i have this error on all devices with api<24.

java.lang.NoClassDefFoundError: org.phoenixframework.PhxSocket$1$1
        at org.phoenixframework.PhxSocket.<init>(PhxSocket.kt:123)

it's impossible to use your lib with this problem. I saw you commited solution for this problem, but it's not released. Is it working? will you release it? when?

ArrayIndexOutOfBoundsException on reconnect

seeing this that takes down the whole app, not sure when it happens

java.lang.ArrayIndexOutOfBoundsException: length=3; index=3
at org.phoenixframework.PhxSocket$reconnectAfterMs$1.invoke(PhxSocket.kt:42)
at org.phoenixframework.PhxSocket$reconnectAfterMs$1.invoke(PhxSocket.kt:26)
at org.phoenixframework.PhxTimer.scheduleTimeout(PhxTimer.kt:31)
at org.phoenixframework.PhxChannel$1.invoke(PhxChannel.kt:71)
at org.phoenixframework.PhxChannel$1.invoke(PhxChannel.kt:6)
at org.phoenixframework.PhxTimer$scheduleTimeout$$inlined$schedule$1.run(Timer.kt:150)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)

Missing proguard rules in README.md

The library in the current state will not work when using proguard.

PhxMessage class member names are used to parse the response from WS.
The solution would be to add @SerializeName annotation, this way there is no need to add any additional proguard rules (apart from the ones for gson and okhhtp).

As for now you have to specify a rule like this
-keepclassmembernames class org.phoenixframework.PhxMessage { *; }

JSON serialization defaults modify property names

When serializing Payload objects, the default JSON behaviour is to convert from camel case to snake case: https://github.com/dsrees/JavaPhoenixClient/blob/master/src/main/kotlin/org/phoenixframework/Defaults.kt#L52

I presume it was done deliberately for internal reasons, but it is a bit of a surprise.

I don't think this is what the equivalent Javascript library does: https://github.com/phoenixframework/phoenix/blob/master/assets/js/phoenix.js#L648

Fatal Exception: java.util.ConcurrentModificationException

I saw a crash lately with the following stack trace. Any idea if this is related to your lib or the way I interact with it? I've been using this one before and nothing like this happened.

Fatal Exception: java.util.ConcurrentModificationException
       at java.util.ArrayList$Itr.next(ArrayList.java:860)
       at org.phoenixframework.PhxChannel.trigger(PhxChannel.kt:384)
       at org.phoenixframework.PhxSocket.triggerChannelError(PhxSocket.kt:381)
       at org.phoenixframework.PhxSocket.remove(PhxSocket.kt:354)
       at org.phoenixframework.PhxSocket.remove(PhxSocket.kt:437)
       at okhttp3.internal.ws.RealWebSocket.failWebSocket(RealWebSocket.java:570)
       at okhttp3.internal.ws.RealWebSocket$2.onFailure(RealWebSocket.java:220)
       at okhttp3.RealCall$AsyncCall.execute(RealCall.java:161)
       at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
       at java.lang.Thread.run(Thread.java:764)

Reconnection strategy documentation

Hi @dsrees
I wanted to update the library but I see that a lot has changed.

Could you provide a bit more information about reconnection strategy?

The reason I'm asking this is that I do have my own reconnection strategy for WS errors and know it would be duplicated with the one provided from the library.
I do see that you do retry only for specific errors and I'm wondering if it would be possible to maybe implement this is a bit more configurable way? So we could choose the strategy like only for errors or when the WS was closed or both. Does it make sense?

Can't connect

Thanks for the library.

I'm having issues connecting and I haven't been able to track down the cause.

  • Server is running correctly.
  • INTERNET and ACCESS_NETWORK_STATE permissions have been added to AndroidManifest.xml.

Here's my activity:

package com.mydomain.myapp

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import org.phoenixframework.PhxSocket

class MainActivity : AppCompatActivity() {
  private val TAG = "MainActivity";

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    this.connectToChatRoom()
  }

  fun connectToChatRoom() {

    // Create the Socket
    val params = hashMapOf("token" to "abc123")
    val url = "http://localhost:4000/socket/websocket"
    val socket = PhxSocket(url, params)

    // Listen to events on the Socket
    socket.logger = { Log.d("TAG", it) }
    socket.onOpen { Log.d("TAG", "Socket Opened") }
    socket.onClose { Log.d("TAG", "Socket Closed") }
    socket.onError { throwable, response ->
      throwable?.printStackTrace()
      Log.d("Message", throwable?.message)
      Log.d("Response:", "${response?.toString()}")
      Log.d(throwable.toString(), "\"Socket Error\"")
    }

    socket.connect()

    // Join channels and listen to events
    val chatroom = socket.channel("user:1")
    chatroom.on("new_message") {
      // `it` is a PhxMessage object
      val payload = it.payload
      Log.d("TAG", "payload received")
    }

    chatroom.join()
      .receive("ok") { /* Joined the chatroom */ }
      .receive("error") { /* failed to join the chatroom */ }
  }
}

And here's my log:

2018-12-14 13:24:30.806 17467-17467/com.mydomain.myapp D/NetworkSecurityConfig: No Network Security Config specified, using platform default
2018-12-14 13:24:30.857 17467-17484/com.mydomain.myapp D/TAG: Transport: error
2018-12-14 13:24:30.859 17467-17484/com.mydomain.myapp W/System.err: java.net.ConnectException: Failed to connect to localhost/127.0.0.1:4000
2018-12-14 13:24:30.859 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.java:242)
2018-12-14 13:24:30.859 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:160)
2018-12-14 13:24:30.859 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:257)
2018-12-14 13:24:30.859 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:135)
2018-12-14 13:24:30.859 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:114)
2018-12-14 13:24:30.859 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:126)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:200)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.RealCall$AsyncCall.execute(RealCall.java:147)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
2018-12-14 13:24:30.860 17467-17484/com.mydomain.myapp W/System.err:     at java.lang.Thread.run(Thread.java:761)
2018-12-14 13:24:30.861 17467-17484/com.mydomain.myapp W/System.err: Caused by: java.net.ConnectException: Connection refused
2018-12-14 13:24:30.861 17467-17484/com.mydomain.myapp W/System.err:     at java.net.PlainSocketImpl.socketConnect(Native Method)
2018-12-14 13:24:30.861 17467-17484/com.mydomain.myapp W/System.err:     at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:334)
2018-12-14 13:24:30.861 17467-17484/com.mydomain.myapp W/System.err:     at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:196)
2018-12-14 13:24:30.861 17467-17484/com.mydomain.myapp W/System.err:     at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:178)
2018-12-14 13:24:30.861 17467-17484/com.mydomain.myapp W/System.err:     at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:356)
2018-12-14 13:24:30.861 17467-17484/com.mydomain.myapp W/System.err:     at java.net.Socket.connect(Socket.java:605)
2018-12-14 13:24:30.861 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.platform.AndroidPlatform.connectSocket(AndroidPlatform.java:71)
2018-12-14 13:24:30.861 17467-17484/com.mydomain.myapp W/System.err:     at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.java:240)
2018-12-14 13:24:30.861 17467-17484/com.mydomain.myapp W/System.err:  ... 21 more
2018-12-14 13:24:30.861 17467-17484/com.mydomain.myapp D/message: Failed to connect to localhost/127.0.0.1:4000
2018-12-14 13:24:30.861 17467-17484/com.mydomain.myapp D/java.net.ConnectException: Failed to connect to localhost/127.0.0.1:4000: "Socket Error"
2018-12-14 13:24:30.861 17467-17484/com.mydomain.myapp D/Response:: null

Any suggestions are appreciated!

Question about the heartbeat

Will the error callback on PhxSocket be called when sending heartbeat fails?

From what I've seen it does not, but what is the purpose of the heartbeat then?

Downgrade OKhttp to 3.12.2

OkHttp terminated support for earlier android versions in 3.13. THis client should not prevent android min target because of a dependency. Retrofit is also pinned to 3.12.2 so this is a safe downgrade

Lots of Timers created

For every channel push a new timer (and thus a new thread) is created. This is quite expensive compared to using a ScheduledThreadPoolExecutor which would be re-using threads.

I plan on trying to convert all usages of Timers to using a shared ScheduledThreadPoolExecutor later in the week if this seems like a good change.

bindings are overwritten

looks like there is a bug in off method of Channel

    public fun off(event: String, ref: Int? = null) {
        // Remove any subscriptions that match the given event and ref ID. If no ref
        // ID is given, then remove all subscriptions for an event.
        this.bindings = bindings
                .filter { it.first == event && (ref == null || ref == it.second) }
                .toMutableList()
    }

If I see this correctly, this removed everything from list that doenst match and not remove the one item :)

This prevents me from seeing any messages.
Maybe I am doing sth wrong but that is what debugging showed me :)

Leaving channel causes timeout

When leaving a channel I receive immediately the events:

Receive: {"event":"phx_reply","payload":{"response":{},"status":"ok"},"ref":"10","topic":"my_topic"}
Receive: {"event":"phx_close","payload":{},"ref":"7","topic":"my_topic"}

However, channel.onClose() is triggered 10 seconds later due to timeout. This happens everytime.

Improve documentation

This library seems very useful. However, the documentation is pretty minimal which makes it hard to get up and running quickly. I'm a beginner/intermediate Android dev and intermediate/advanced Elixir dev and I still haven't figured out how to connect successfully #29.

I may be doing something plainly wrong, but the docs aren't helping me much. I'd be happy to make a PR with more detailed documentation for getting started if others agree. @dsrees

Remove GSON dependency

Ideally, the User of this client should be able to use any JSON library of their choosing. Initial thought is to create a JsonConverter interface that accepts raw JSON string and has it pass back the decoded object that we need. This would also require a Builder of some sort to construct the PhxSocket class with the JsonConverter

Any ideas are welcome on this.

Socket.removeFromSendBuffer --> NullPointerException

Hello. I have faced with the crash after the device was sleeping for a while.

Steps to reproduce:
1 - Start socket session in an app
2 - Switch a device to the sleep mode
3 - Exit from sleep mode after a while

E AndroidRuntime:   at java.lang.Thread.run(Thread.java:764)
E AndroidRuntime:    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
E AndroidRuntime:    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
E AndroidRuntime:    at okhttp3.internal.connection.RealCall$AsyncCall.run(SourceFile:519)
E AndroidRuntime:    at okhttp3.internal.ws.RealWebSocket$connect$1.onResponse(SourceFile:197)
E AndroidRuntime:    at okhttp3.internal.ws.RealWebSocket.failWebSocket(SourceFile:592)
E AndroidRuntime:    at org.phoenixframework.WebSocketTransport.onFailure(SourceFile:136)
E AndroidRuntime:    at org.phoenixframework.Socket$connect$3.invoke(SourceFile:320)
E AndroidRuntime:    at org.phoenixframework.Socket$connect$3.invoke(SourceFile:320)
E AndroidRuntime:    at org.phoenixframework.Socket.onConnectionError$JavaPhoenixClient(SourceFile:603)
E AndroidRuntime:    at org.phoenixframework.Socket.triggerChannelError(SourceFile:455)
E AndroidRuntime:    at org.phoenixframework.Channel.trigger$JavaPhoenixClient$default(SourceFile:392)
E AndroidRuntime:    at org.phoenixframework.Channel.trigger$JavaPhoenixClient(SourceFile:399)
E AndroidRuntime:    at org.phoenixframework.Channel.trigger$JavaPhoenixClient(SourceFile:409)
E AndroidRuntime:    at org.phoenixframework.Channel$10.invoke(SourceFile:206)
E AndroidRuntime:    at org.phoenixframework.Channel$10.invoke(SourceFile:214)
E AndroidRuntime:    at org.phoenixframework.Socket.removeFromSendBuffer$JavaPhoenixClient(SourceFile:471)
E AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object kotlin.Pair.getFirst()' on a null object reference

Device: Samsung S8+ (9.0)
Version: com.github.dsrees:JavaPhoenixClient: 1.2.0

Heartbeat seems to be sent only once (instead of every x seconds)

After upgrading from 0.1.8 to the latest version (0.2.5) we are noticing that the Heardbeats are not sent regularly anymore. Instead they seem to be sent only once after the connexion is established.

After looking quickly at the code it seems that the hearbeat scheduling is done in Socket, line 370:

dispatchQueue.queue(heartbeatInterval, TimeUnit.MILLISECONDS) { sendHeartbeat() }

And looking at the implementation (dispatchQueue is a ScheduledDispatchQueue), I see that it's based on ScheduledThreadPoolExecutor.schedule which is a one-shot API, not a periodic one.

Is this correct?

connection needs to be cleared after an error

If the socket connection is lost due to a loss in network connectivity, the connection needs to be reset. Example of this is to open a websocket and then switch to Airplane mode on Android. This will trigger an SSLException, killing the connection. However socket.isConnected will still return true until the heartbeat is not responded to.

javax.net.ssl.SSLException: Write error: ssl=0x744bbdcd08: I/O error during system call, Broken pipe
	at com.android.org.conscrypt.NativeCrypto.SSL_write(Native Method)
	at com.android.org.conscrypt.NativeSsl.write(NativeSsl.java:414)
	at com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream.write(ConscryptFileDescriptorSocket.java:623)
	at okio.Okio$1.write(Okio.java:79)
	at okio.AsyncTimeout$1.write(AsyncTimeout.java:180)
	at okio.RealBufferedSink.flush(RealBufferedSink.java:224)
	at okhttp3.internal.ws.WebSocketWriter.writeControlFrame(WebSocketWriter.java:146)
	at okhttp3.internal.ws.WebSocketWriter.writeClose(WebSocketWriter.java:106)
	at okhttp3.internal.ws.RealWebSocket.writeOneFrame(RealWebSocket.java:507)
	at okhttp3.internal.ws.RealWebSocket$1.run(RealWebSocket.java:159)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:458)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
	at java.lang.Thread.run(Thread.java:764)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.