208 lines
7.3 KiB
JavaScript
208 lines
7.3 KiB
JavaScript
/**
|
|
* Phoenix Channels JavaScript client
|
|
*
|
|
* ## Socket Connection
|
|
*
|
|
* A single connection is established to the server and
|
|
* channels are multiplexed over the connection.
|
|
* Connect to the server using the `Socket` class:
|
|
*
|
|
* ```javascript
|
|
* let socket = new Socket("/socket", {params: {userToken: "123"}})
|
|
* socket.connect()
|
|
* ```
|
|
*
|
|
* The `Socket` constructor takes the mount point of the socket,
|
|
* the authentication params, as well as options that can be found in
|
|
* the Socket docs, such as configuring the `LongPoll` transport, and
|
|
* heartbeat.
|
|
*
|
|
* ## 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:
|
|
*
|
|
* ```javascript
|
|
* let channel = socket.channel("room:123", {token: roomToken})
|
|
* channel.on("new_msg", msg => console.log("Got message", msg) )
|
|
* $input.onEnter( e => {
|
|
* channel.push("new_msg", {body: e.target.val}, 10000)
|
|
* .receive("ok", (msg) => console.log("created message", msg) )
|
|
* .receive("error", (reasons) => console.log("create failed", reasons) )
|
|
* .receive("timeout", () => console.log("Networking issue...") )
|
|
* })
|
|
*
|
|
* channel.join()
|
|
* .receive("ok", ({messages}) => console.log("catching up", messages) )
|
|
* .receive("error", ({reason}) => console.log("failed join", reason) )
|
|
* .receive("timeout", () => console.log("Networking issue. Still waiting..."))
|
|
*```
|
|
*
|
|
* ## Joining
|
|
*
|
|
* Creating a channel with `socket.channel(topic, params)`, binds the params to
|
|
* `channel.params`, which are sent up on `channel.join()`.
|
|
* Subsequent rejoins will send up the modified params for
|
|
* updating authorization params, or passing up last_message_id information.
|
|
* Successful joins receive an "ok" status, while unsuccessful joins
|
|
* receive "error".
|
|
*
|
|
* With the default serializers and WebSocket transport, JSON text frames are
|
|
* used for pushing a JSON object literal. If an `ArrayBuffer` instance is provided,
|
|
* binary encoding will be used and the message will be sent with the binary
|
|
* opcode.
|
|
*
|
|
* *Note*: binary messages are only supported on the WebSocket transport.
|
|
*
|
|
* ## 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.
|
|
*
|
|
*
|
|
* ## Socket Hooks
|
|
*
|
|
* Lifecycle events of the multiplexed connection can be hooked into via
|
|
* `socket.onError()` and `socket.onClose()` events, ie:
|
|
*
|
|
* ```javascript
|
|
* socket.onError( () => console.log("there was an error with the connection!") )
|
|
* socket.onClose( () => console.log("the connection dropped") )
|
|
* ```
|
|
*
|
|
*
|
|
* ## Channel Hooks
|
|
*
|
|
* For each joined channel, you can bind to `onError` and `onClose` events
|
|
* to monitor the channel lifecycle, ie:
|
|
*
|
|
* ```javascript
|
|
* channel.onError( () => console.log("there was an error!") )
|
|
* channel.onClose( () => console.log("the channel has gone away 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.
|
|
*
|
|
* ### onClose hooks
|
|
*
|
|
* `onClose` hooks are invoked only in two cases. 1) the channel explicitly
|
|
* closed on the server, or 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:
|
|
*
|
|
* ```javascript
|
|
* let channel = socket.channel("some:topic")
|
|
* let presence = new 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:
|
|
*
|
|
* ```javascript
|
|
* 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:
|
|
*
|
|
* ```javascript
|
|
* let listBy = (id, {metas: [first, ...rest]}) => {
|
|
* first.count = rest.length + 1 // count of this user's presences
|
|
* first.id = id
|
|
* return first
|
|
* }
|
|
* let 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:
|
|
*
|
|
* ```javascript
|
|
* let presence = new Presence(channel)
|
|
*
|
|
* // detect if user has joined for the 1st time or from another tab/device
|
|
* presence.onJoin((id, current, newPres) => {
|
|
* if(!current){
|
|
* console.log("user has entered for the first time", newPres)
|
|
* } else {
|
|
* console.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.metas.length === 0){
|
|
* console.log("user has left from all devices", leftPres)
|
|
* } else {
|
|
* console.log("user left from a device", leftPres)
|
|
* }
|
|
* })
|
|
* // receive presence data from server
|
|
* presence.onSync(() => {
|
|
* displayUsers(presence.list())
|
|
* })
|
|
* ```
|
|
* @module phoenix
|
|
*/
|
|
|
|
import Channel from "./channel"
|
|
import LongPoll from "./longpoll"
|
|
import Presence from "./presence"
|
|
import Serializer from "./serializer"
|
|
import Socket from "./socket"
|
|
|
|
export {
|
|
Channel,
|
|
LongPoll,
|
|
Presence,
|
|
Serializer,
|
|
Socket
|
|
}
|