Skip to content

Commit 12fb217

Browse files
authored
treat 410 as error if we already have a token (#6538)
* treat 410 as error if we already have a token Relates to: https://elixirforum.com/t/handling-channels-liveview-longpoll-fallback-unmatched-topic/72378 On mobile Safari (e.g. iOS) the following could happen when using LongPoll: 1. user connects to a channel 2. device goes to sleep 3. Phoenix shuts down long poll server 4. client connects again, server responds with 410 5. LongPoll continues as usual, but channels are not joined any more 6. client sends messages for previous channel 7. channel errors with "unmatched topic" but does not rejoin We handle this by checking if we already have a LongPoll token when receiving a 410 code and instead trigger an error which will cause a proper rejoin. * add unit test
1 parent 9f13f00 commit 12fb217

File tree

2 files changed

+31
-3
lines changed

2 files changed

+31
-3
lines changed

assets/js/phoenix/longpoll.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ export default class LongPoll {
7171
this.ajax("GET", headers, null, () => this.ontimeout(), resp => {
7272
if(resp){
7373
var {status, token, messages} = resp
74+
if(status === 410 && this.token !== null){
75+
// In case we already have a token, this means that our existing session
76+
// is gone. We fail so that the client rejoins its channels.
77+
this.onerror(410)
78+
this.closeAndRetry(3410, "session_gone", false)
79+
return
80+
}
7481
this.token = token
7582
} else {
7683
status = 0

assets/test/longpoll_test.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,11 @@ describe("LongPoll", () => {
8383
const authToken = "my-auth-token"
8484
const encodedToken = btoa(authToken)
8585
const protocols = ["phoenix", `${AUTH_TOKEN_PREFIX}${encodedToken}`]
86-
86+
8787
const longpoll = new LongPoll("http://localhost/socket/longpoll", protocols)
8888
longpoll.timeout = 1000
8989
longpoll.poll()
90-
90+
9191
// Verify Ajax.request was called with the correct headers
9292
expect(Ajax.request).toHaveBeenCalledWith(
9393
"GET",
@@ -104,7 +104,7 @@ describe("LongPoll", () => {
104104
const longpoll = new LongPoll("http://localhost/socket/longpoll", undefined)
105105
longpoll.timeout = 1000
106106
longpoll.poll()
107-
107+
108108
// Verify Ajax.request was called without auth token header
109109
expect(Ajax.request).toHaveBeenCalledWith(
110110
"GET",
@@ -116,6 +116,27 @@ describe("LongPoll", () => {
116116
expect.any(Function)
117117
)
118118
})
119+
120+
it("should treat 410 as error when token already exists", () => {
121+
const longpoll = new LongPoll("http://localhost/socket/longpoll", undefined)
122+
longpoll.timeout = 1000
123+
longpoll.token = "existing-token"
124+
125+
const mockOnerror = jest.fn()
126+
const mockCloseAndRetry = jest.fn()
127+
longpoll.onerror = mockOnerror
128+
longpoll.closeAndRetry = mockCloseAndRetry
129+
130+
Ajax.request.mockImplementation((method, url, headers, body, timeout, ontimeout, callback) => {
131+
callback({status: 410, token: "new-token", messages: []})
132+
return {abort: jest.fn()}
133+
})
134+
135+
longpoll.poll()
136+
137+
expect(mockOnerror).toHaveBeenCalledWith(410)
138+
expect(mockCloseAndRetry).toHaveBeenCalledWith(3410, "session_gone", false)
139+
})
119140
})
120141

121142
describe("batchSend", () => {

0 commit comments

Comments
 (0)