Skip to content

Commit

Permalink
Update readme with origina javascript library documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
kuon committed Feb 12, 2020
1 parent 1992444 commit 62d735c
Showing 1 changed file with 226 additions and 1 deletion.
227 changes: 226 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import ch.kuon.phoenix.Channel
import ch.kuon.phoenix.Presence


fun test() {
fun doSomething() {
val url = "ws://localhost:4444/socket"
val sd = Socket(url)

Expand All @@ -81,6 +81,9 @@ fun test() {
.receive("error") { msg ->
// channel did not connected
}
.receive("timeout") { msg ->
// connection timeout
}

chan
.push("hello")
Expand All @@ -89,7 +92,229 @@ fun test() {
}

}
```

## Important Notes

- API should be thread safe, but they use a naive locking mechanism (over the
socket object).
- API can be used from main UI thread as minimal work is done on the calling
thread.
- Callbacks can be called on any thread, be sure to take this into account.
- Callbacks mut be thread safe.
- Be sure to disconnect the socket in your cleanup code, this is not done
automatically.

## Sockets

### Connection

A single websocket connection is established to the server and channels are
multiplexed over the single connection.

Connect to the server using the `Socket` class:


```kotlin
val opts = Socket.Options()
opts.timeout = 5_000 // socket timeout in milliseconds
opts.heartbeatIntervalMs = 10_000 // heartbeat intervall in milliseconds
opts.rejoinAfterMs = {tries -> tries * 500} // rejoin timer function
opts.reconnectAfterMs = {tries -> tries * 500} // reconnect timer function
opts.logger = {tag, msg -> com.android.Log.d(tag, msg)} // log message
opts.params = hashMap("user_token" to "supersecret") // params

// opts can be omitted for most uses
val socket = Socket("ws://myapp.com/socket", opts)
socket.connect()
```

### Hooks

Lifecycle events of the multiplexed connection can be hooked into via
`socket.onError()` and `socket.onClose()` events, ie:

```kotlin
socket.onError { System.out.println("There was an error with the connection!") }
socket.onClose { System.out.println("The connection closed!") }
```

## Channels

Channels are isolated, concurrent processes on the server that
subscribe to topics and broker events between the client and server.

To join a channel, you must provide the topic and channel params for
authorization. Here's an example chat room example where `"new_msg"`
events are listened for, messages are pushed to the server, and
the channel is joined with ok/error/timeout matches:


```kotlin
val channel = socket.channel(
"room:123",
JSONObject(hashMap("token" to roomToken))
)
channel.on("new_msg") { msg ->
System.out.println("Got a message: " + msg.response.toString())
}
someTextInput.onClick {
channel
.push("new_msg", JSONObject(hashMap("data" to "somedata")))
.receive("ok") { _ ->
System.out.println("Created msg")
}
.receive("error") { reason ->
System.out.println("Got an error: " + reason)
}
.receive("timeout") {
System.out.println("Timeout!")
}
}
channel
.join()
.receive("ok") { msg ->
System.out.println("Join success, got messages: " + msg.toString())
}
.receive("error") { reason ->
System.out.println("Failed to join because: " + reason)
}
.receive("timeout") {
System.out.println("Join timeout!")
}
```

### Joining

When channels are created with `socket.channel(topic, params)`, `params` is
bound to the channel and sent on `join()`.

`join()` can only be called once, but channel might rejoin on timeout or other
error.

Successful joins receive an "ok" status, while unsuccessful joins
receive "error".

### Duplicate Join Subscriptions

While the client may join any number of topics on any number of channels,
the client may only hold a single subscription for each unique topic at any
given time. When attempting to create a duplicate subscription,
the server will close the existing channel, log a warning, and
spawn a new channel for the topic. The client will have their
`channel.onClose()` callbacks fired for the existing channel, and the new
channel join will have its receive hooks processed as normal.

## Pushing Messages

From the previous example, we can see that pushing messages to the server
can be done with `channel.push(eventName, payload)` and we can optionally
receive responses from the push. Additionally, we can use
`receive("timeout", callback)` to abort waiting for our other `receive` hooks
and take action after some period of waiting. The default timeout is 10000ms.

### Hooks

For each joined channel, you can bind to `onError` and `onClose` events
to monitor the channel lifecycle, ie:

```javascript
channel.onError { System.out.println("There was an error!") }
channel.onClose { System.out.println("The was closed gracefully!") }
```

### onError hooks

`onError` hooks are invoked if the socket connection drops, or the channel
crashes on the server. In either case, a channel rejoin is attempted
automatically in an exponential backoff manner (the timer can be altered with
socket option `rejoinAfterMs`).

### onClose hooks

`onClose` hooks are invoked only in two cases:

1) The channel explicitly closed on the server.
2) The client explicitly closed, by calling `channel.leave()`

## Presence

The `Presence` object provides features for syncing presence information
from the server with the client and handling presences joining and leaving.

### Syncing state from the server

To sync presence state from the server, first instantiate an object and
pass your channel in to track lifecycle events:

```kotlin
val channel = socket.channel("some:topic")
val presence = Presence(channel)
```

Next, use the `presence.onSync` callback to react to state changes
from the server. For example, to render the list of users every time
the list changes, you could write:

```kotlin
presence.onSync {
myRenderUsersFunction(presence.list())
}
```


### Listing Presences

`presence.list()` is used to return a list of presence information
based on the local state of metadata. By default, all presence
metadata is returned, but a `listBy` function can be supplied to
allow the client to select which metadata to use for a given presence.

For example, you may have a user online from different devices with
a metadata status of "online", but they have set themselves to "away"
on another device. In this case, the app may choose to use the "away"
status for what appears on the UI. The example below defines a `listBy`
function which prioritizes the first metadata which was registered for
each user. This could be the first tab they opened, or the first device
they came online from:

```kotlin
val listBy = { id, metas ->
return metas.get(0)
}
val onlineUsers = presence.list(listBy)
```

### Handling individual presence join and leave events

The `presence.onJoin` and `presence.onLeave` callbacks can be used to
react to individual presences joining and leaving the app. For example:

```kotlin
val presence = Presence(channel)

// detect if user has joined for the 1st time or from another tab/device
presence.onJoin { id, current, newPres ->
if(current != null) {
log("user has entered for the first time", newPres)
} else {
log("user additional presence", newPres)
}
}

// detect if user has left from all tabs/devices, or is still present
presence.onLeave { id, current, leftPres ->
if(current.getMetas().length() === 0){
log("user has left from all devices", leftPres)
} else {
log("user left from a device", leftPres)
}
}

// receive presence data from server
presence.onSync {
displayUsers(presence.list())
}
```


Expand Down

0 comments on commit 62d735c

Please sign in to comment.