Alpaca WebSocket authentication
I was trying to stream market data from Alpaca's WebSocket endpoint.
Alpaca's documentation lists the following three steps to receive market data from the stream.
- Connect to the endpoint.
- Send an authentication frame.
- Send a subscription frame.
Step 2 proved to be tricky.
The library I'm using is WebSockex. Per best practice, you should either send the auth frame in the handle_connect function or reply with an auth frame inside handle_frame when the “connected” message is received. In this particular case, both would fail or time out.
The handle_connect function is called immediately after the WebSocket connection is established. Inside handle_connect, you cannot return {:reply, auth_frame, state}. You can only return {:ok, state} or {:close, state}.
If you want to send the auth frame to the server, you'll have to use WebSockex.send_frame(pid, frame). ChatGPT keeps telling me to do the following, although it invariably causes a CallingSelfError. Apparently, you cannot just ask yourself (the same process) to send a frame.
1def handle_connect(_conn, state) do
2 # This will cause an error.
3 WebSockex.send_frame(self(), {:text, auth_frame})
4 {:ok, state}
5end
But in handle_frame function, you can return a tuple like this: {:reply, auth_frame, state}. The only thing you need to do is pattern match incoming frames. When you see "connected", return that reply tuple. This should have worked if it didn't time out. (Curiously, replying with a subscription frame worked.)
In the end, I did the following: I sent the auth frame inside the start_link function.
1 def start_link(state) do
2 connected = WebSockex.start_link(@uri, __MODULE__, state)
3
4 case connected do
5 {:ok, pid} ->
6 WebSockex.send_frame(pid, {:text, auth_json})
7 {:ok, pid}
8
9 {:error, reason} ->
10 Logger.info(reason)
11 end
12 end
