# `ALLM.Providers.Support.WebSocket`
[🔗](https://github.com/cykod/ALLM/blob/v0.6.1/lib/allm/providers/support/web_socket.ex#L1)

The WebSocket transport seam the streaming audio adapters are written
against.

Layer B — runtime. An adapter takes the implementing module from
`opts[:ws_module]` (default `ALLM.Providers.Support.WebSocket.Mint`),
exactly as the HTTP stream adapters take `opts[:finch_module]`, so a test
can substitute a scripted stub without a network.

## Transport rule

A WebSocket is opened over an HTTP/1 connection in the process that
reduces the stream, and every function of this behaviour is called from
that process. The connection is process-less: its transport messages
arrive in the reducing process's mailbox, next to any input-pump
messages, and a halted stream closes the socket and drains those
messages in its after function. The API key goes in the upgrade
request's headers, never in the URL, because URLs reach logs and
telemetry.

## Receiving selectively

The owner must not consume messages that are not its own, so it selects
transport messages in its `receive` by tag: every message a connection
delivers is a 2- or 3-tuple whose second element is `message_tag/1` of
that connection (for the Mint implementation, the socket). The
`is_transport_message/2` guard selects exactly those, and a matching
message goes to `handle_message/2`:

    require ALLM.Providers.Support.WebSocket, as: WebSocket
    tag = ws.message_tag(conn)

    receive do
      message when WebSocket.is_transport_message(message, tag) ->
        ws.handle_message(conn, message)
    end

`handle_message/2` still returns `:unknown` for a message that carries
the tag but is not a transport message, so a caller that passes it
anything else gets a plain answer.

## Frames

Control frames never leave the implementation. A server ping is answered
with a pong by `handle_message/2` itself, and pongs are dropped, so
`t:frame/0` has only data and close variants. `:closed` in a
`handle_message/2` result means the transport closed without a close
frame.

# `conn`

```elixir
@type conn() :: term()
```

An open connection. Opaque to the adapter; only this behaviour's functions read it.

# `connect_error`

```elixir
@type connect_error() ::
  {:upgrade_status, pos_integer(), map() | list() | binary()}
  | {:transport, term()}
```

Why `connect/3` failed: the server answered the upgrade with a status
other than 101 (with the response body, JSON-decoded when it is JSON), or
the transport failed before a response.

# `frame`

```elixir
@type frame() ::
  {:text, String.t()}
  | {:binary, binary()}
  | {:close, non_neg_integer() | nil, String.t()}
```

A data or close frame, sent or received.

# `close`

```elixir
@callback close(conn()) :: :ok
```

Close the connection: a best-effort close frame, then the transport.
Idempotent; never raises.

# `connect`

```elixir
@callback connect(
  url :: String.t(),
  headers :: [{String.t(), String.t()}],
  opts :: keyword()
) ::
  {:ok, conn()} | {:error, connect_error()}
```

Open a WebSocket to `url` (`wss://` or `ws://`) with `headers` on the
upgrade request, and drive the handshake to completion in the calling
process. Honours `opts[:connect_timeout]` (milliseconds).

# `flush_messages`

```elixir
@callback flush_messages(conn()) :: :ok
```

Remove every transport message of `conn` from the calling process's
mailbox. Call it after `close/1`.

# `handle_message`

```elixir
@callback handle_message(conn(), message :: term()) ::
  {:ok, conn(), [frame() | :closed]} | :unknown | {:error, conn(), term()}
```

Interpret one message selected by `message_tag/1`. Returns the data and
close frames it carried (answering pings itself), `:unknown` for a
message that is not this connection's, or an error.

# `message_tag`

```elixir
@callback message_tag(conn()) :: term()
```

The tag every transport message of `conn` carries as its second element.

# `send_frame`

```elixir
@callback send_frame(conn(), frame()) :: {:ok, conn()} | {:error, conn(), term()}
```

Send one frame.

# `is_transport_message`
*macro* 

Guard: `message` is a transport message of the connection whose
`message_tag/1` is `tag` (a 2- or 3-tuple whose second element is
`tag`). Use it to select transport messages in a selective `receive`, so
no other message in the owner's mailbox is consumed.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
