Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@

## Enhancements

* [phoenix.js] Long polling retries interrupted requests without rejoining
channels while its server session remains alive. Messages are retained until
acknowledged and retried pushes are deduplicated. New `socket.onSuspend` and
`socket.onResume` callbacks report interruptions and recovery without a
channel error. Older servers retain the existing reconnect behavior. During
a rolling deploy, a session that reaches a node with an older server falls
back to that behavior for the rest of the session.
* [Phoenix.Endpoint] Add the `:resumable` WebSocket option. Clients that use the
`resumable: true` option of the `phoenix.js` Socket resume the socket over a new
WebSocket after the connection failed or the heartbeat timed out, without
rejoining channels. Both sides send the messages the other side missed. With
`longPollFallbackMs`, clients continue the socket with long polling when they
cannot resume it over a WebSocket in time. Clients connecting to servers without
the option behave like before. Pass an MFA tuple instead of `true` to decide for
each connection, for example to only let signed-in users resume
* [Phoenix.Socket] Add the optional `c:Phoenix.Socket.resume/3` callback to check
a client that resumes its session after a failed request or WebSocket, for example
to verify that the user is still logged in. Returning `:error` stops the socket,
so the client connects again. Custom transports can implement the new optional
`c:Phoenix.Socket.Transport.handle_resume/2` callback
* The longpoll session token is now sent in a `x-phoenix-longpoll-token`
header instead of the query string. If you do rolling deploys where both
old and new nodes are active at the same time, ensure that you deploy
Expand Down
3 changes: 3 additions & 0 deletions assets/js/phoenix/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ export default class Connection {
this.channelsErrored = false
// set once the close event was handled, as LongPoll can emit it more than once
this.closeHandled = false
this.suspended = false
transport.onopen = () => { if(this.isCurrent()){ socket.onConnOpen() } }
transport.onerror = error => { if(this.isCurrent()){ socket.onConnError(error) } }
transport.onmessage = event => { if(this.isCurrent()){ socket.onConnMessage(event) } }
transport.onsuspend = () => { if(this.isCurrent()){ socket.onConnSuspend(this) } }
transport.onresume = () => { if(this.isCurrent()){ socket.onConnResume(this) } }
transport.onclose = event => {
if(this.isCurrent() && !this.closeHandled){
this.closeHandled = true
Expand Down
2 changes: 2 additions & 0 deletions assets/js/phoenix/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export const SOCKET_STATES = {connecting: 0, open: 1, closing: 2, closed: 3}
export const MAX_LONGPOLL_BATCH_SIZE = 100;
export const DEFAULT_TIMEOUT = 10000
export const WS_CLOSE_NORMAL = 1000
// the close code of a transport whose session is gone, so the socket connects again
export const CLOSE_SESSION_GONE = 3410
export const CHANNEL_STATES = {
closed: "closed",
errored: "errored",
Expand Down
32 changes: 32 additions & 0 deletions assets/js/phoenix/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,36 @@
* socket.onClose( () => console.log("the connection dropped") )
* ```
*
* LongPoll automatically retries interrupted requests while the server keeps
* its session alive. During recovery, channels stay joined and outgoing messages
* are buffered. Use `onSuspend` and `onResume` to show connection status without
* resetting channel state:
*
* ```
* socket.onSuspend( () => console.log("recovering the connection") )
* socket.onResume( () => console.log("the connection recovered") )
* ```
*
* Recovery requires a server that supports acknowledged long-poll messages.
* With an older server, and for the rest of a session that reached a node
* with an older server during a rolling upgrade, failed requests close the
* socket and channels rejoin. If the session expires, the usual error/close events
* fire and channels rejoin. Push timeouts continue to apply while suspended.
*
* WebSockets resume the same way with the `resumable` option, which requires
* the `:resumable` WebSocket option on the server:
*
* ```
* let socket = new Socket("/socket", {resumable: true})
* ```
*
* When the WebSocket fails, or the heartbeat times out, the socket resumes its
* session over a new WebSocket and both sides send the messages the other side
* missed. With the `longPollFallbackMs` option, the socket continues the session
* with long polling when it cannot resume it over a WebSocket within that time,
* for example when a proxy stops allowing WebSockets. Servers that don't support
* it behave like before.
*
*
* ## Channel Hooks
*
Expand Down Expand Up @@ -195,13 +225,15 @@
import Channel from "./channel"
import LongPoll from "./longpoll"
import Presence from "./presence"
import ResumableWebSocket from "./resumable_websocket"
import Serializer from "./serializer"
import Socket from "./socket"

export {
Channel,
LongPoll,
Presence,
ResumableWebSocket,
Serializer,
Socket
}
Loading
Loading