Proposal:
I wish the socket connect/3
callback could allow returning a reply to the frontend, which would then be visible in phoenix.js as an argument to the callback given to socket.onOpen
.
I think this would be straightforward to add to successful connections, which is my main request.
It would be harder but even more useful if a reason could be passed to the frontend for rejecting a connection.
Use Case:
I want to reject a socket connection and tell the frontend why.
Currently, phoenix can use the error_handler
config (docs) to say why a connection was rejected, and this reaches the browser (it’s visible in dev tools) but it’s not visible to phoenix.js. The arguments to the onError
and onClose
callbacks are identical no matter what error is sent, and they’re even identical to the error that phoenix.js receives when there’s a network error.
My JS app needs to tell different connection errors apart in order to handle them differently. On a network error it should try again (the deafult). On an authn error it should not try again and should redirect to the login page. If the frontend app version is outdated it should refresh. Other applications may have app-specific handlers for other errors.
Workaround:
Because I can’t send a useful error message, I allow the socket to connect, even if there are authentication problems, and then store the auth details in socket.assigns, and check authentication on every channel join.
Then, I wish I could reply to the socket connect to tell the client about any authentication problems so that the frontend could handle them. But there’s no way to reply with a payload to socket connect/3. So instead, I need a workaround within my workaround:
After successfully connecting to a socket, the client immediately joins a channel within that socket (I named it “metadata”), and that channel replies to the join with a payload that describes any authentication or other problems that the client should handle in a custom way.
If a socket fails to connect, the client can know that it’s definitely because of network problems, not auth problems, and it can retry in the default way.
Including a reply with a rejected socket connection:
This would most directly address my use case, but might be difficult. There’s probably a reason that browsers don’t expose websocket connection failures to JS. It might require backwards incompatible changes to how error handlers for connect/3 are done. I’d like this idea to be at least considered, but it’s okay with me if there’s reasons not to do it.
I saw a number of other threads on this forum and others about this issue, so I think it’s something that many people are running into.
Including a reply with an accepted socket connection:
This would make my workaround much simpler. I wouldn’t need the separate “metadata” channel, and it’d save a network round trip during the connection process. I could send any authentication problems or other metadata directly via the socket reply, and the client could handle it immediately after connecting.
I expect it’d be relatively easy to implement. The interface would be an extra {:ok, reply, socket}
case to the return type of connect/3
, and an argument passed to the onOpen
callback, both of which are backwards-compatible changes.
I haven’t seen others asking for this idea like I’ve seen others wanting to reply with a rejection reason, but I think it would still be generally useful. Not just for authentication issues like me, but I’m sure there are plenty of application-specific use cases for something like this, like replying with user metadata that the client didn’t know before joining.
This feature is my main suggestion and reason for making this post.
The only risk I can see with something like this is that being able to send data on a socket outside of a channel (even if it’s just a one-time reply) could make learning the distinction between sockets and channels more confusing. I can imagine people asking when to use socket replies and when to use channel replies. But this is minor.
Summary:
It’d be nice if it was possible to reply to a socket connection (maybe rejection reasons, but probably just accepted connections) with a payload.