Dynamically select Phoenix Channel to join

Is it possible to dynamically choose the Phoenix Channel that a client joins? I know in the UserSocket you can use channel "room:*", MyApp.RoomChannel and then treat the different suffixes to room: differently, but all the sessions will be directly connected to MyApp.RoomChannel.

My main issue right now is am trying to load a different channel depending on the mobile client version that is connecting. And a difficulty that I forsee trying to set different intercepts for each channel.

How many version do you expect? Is channel "v1:room:*", MyApp.V1.RoomChannel out of the question?

It’s not completely out of the question, but I was hoping to split off the old client into it’s own channel while keeping the channel name as-is. However I am completely unable to change the old client.

I guess I could bifurcate at the UserSocket level into UserSocket and UserSocketOldClient but I’d prefer to do it at the channel level if I can.

So it seems like the macros like channel "v1:room:*", MyApp.V1.RoomChannel generate a bunch of __channel__/1 functions which are then called here https://github.com/phoenixframework/phoenix/blob/v1.4/lib/phoenix/socket.ex#L678-L695.

Maybe you could call Phoenix.Channel.Server.join/4 with the proper channel module “manually” somewhere? One way (maybe after a PR, unless there is a better way) would probably be the ability to provide a callback function to def handle_in(nil, %{event: "phx_join", ... like on_join which would receive the socket, opts, and some other params and return {channel, opts} instead of __channel__.

  defp handle_in(nil, %{event: "phx_join", topic: topic, ref: ref} = message, state, socket) do
    {channel, opts} = if :erlang.function_exported(socket.handler, :on_join, 2) do
      socket.handler.on_join(topic, socket)
     else
       socket.handler.__channel__(topic)
      # ...
  end

The easiest answer is to have your RoomChannel simply delegate to other modules with the client specific callbacks, except you want to intercept, which only works here by unnecessarily intercepting some messages in either channel. So ask yourself if you really need intercepting. In many cases, the client can filter messages for example. Worst case, you Desktop and Mobile room channel modules just push(socket, event, payload) on the intercepted messages that they don’t care about intercepting.

While it looks like that approach would work, unfortunately Phoenix.Channel.Server is not part of the public API so I wouldn’t want to use that in production.

Maybe a PR could be created that would modify Phoenix.Socket.channel/3 to optionally accept a function as the second argument (instead of the Channel Module). The function would take in the socket and return the Channel Module. I actually quite like that approach.