# `HTTP.WebSocket`
[🔗](https://github.com/gsmlg-dev/http_fetch/blob/v0.21.17/apps/http_web_socket/lib/http/web_socket.ex#L1)

Browser-like WebSocket client API for Elixir.

Events are delivered as messages to the owner process:

    {HTTP.WebSocket, socket, %HTTP.WebSocket.Event.Open{}}
    {HTTP.WebSocket, socket, %HTTP.WebSocket.Event.Message{}}
    {HTTP.WebSocket, socket, %HTTP.WebSocket.Event.Error{}}
    {HTTP.WebSocket, socket, %HTTP.WebSocket.Event.Close{}}

HTTP/1 is the default. Select `http_version: :http2`, `:h2c`, or `:auto`
explicitly; `http_version/1` reports the negotiated HTTP version, while
`protocol/1` continues to report the selected WebSocket subprotocol.

With `delivery: :ack`, message events include a delivery reference:

    {HTTP.WebSocket, socket, %HTTP.WebSocket.Event.Message{}, ref}

Call `acknowledge/2` after consuming each message. Open, Error, and Close
retain their three-element event envelopes. Receive queues, pending sends,
and fragmented messages have finite configurable limits. Established sessions
default to `idle_timeout: :infinity`; opening and close deadlines are separate.

Telemetry URLs omit query, userinfo, authority, and fragment by default.
Pass `telemetry_url: nil` to omit all URL-derived telemetry metadata, or a
URI/string containing only caller-approved fields to replace the telemetry
URL (for example, `telemetry_url: "ws://127.0.0.1:8080"` omits a private
path). Invalid replacements reject construction; replacement credentials
are always stripped. This option applies to every lifecycle/message event
and never changes the transport URL or handshake.

## HTTP/1 WebSocket proxy boundary

For frame forwarding, select `mode: :proxy`, `http_version: :http1`,
`delivery: :ack` and `automatic_pong: false`. Proxy mode delivers
`%HTTP.WebSocket.Event.Frame{opcode: opcode, data: binary}` for text, binary,
ping and pong, with the same delivery reference/`acknowledge/2` contract.
`binary_type` does not alter proxy frame data. Close retains its lifecycle
event and automatic reply. `send_frame/2` and `send_frame_ack/2` accept
`{opcode, binary}` for those four opcodes. Automatic pong defaults to true;
disabling it requires proxy mode. Proxy `close/3` accepts nil with an empty
reason, 1000-1014 except 1004/1005/1006, and 3000-4999. Reserved/non-wire
codes, invalid UTF-8 and reasons over 123 bytes remain rejected.

Proxy HTTP/2, h2c and auto selection return `{:error, :proxy_requires_http1}`.
Wait for successful completion of **every** `send_frame_ack/2` reference
before initiating Close: local close discards queued application frames in
both HTTP versions. This provides ordered local write completion over HTTP/1;
proxy mode supplies no HTTP/2 drain contract. Control sends share the bounded
application send FIFO, including zero-length payloads. Control delivery uses
the receive limits too; ACK capacity must accommodate at least 132 bytes.

Use this public client for the upstream half of a WebSocket proxy. Select
`http_version: :http1` and `delivery: :ack`; acknowledge each Message only
after downstream consumption. Set finite `opening_timeout`, `idle_timeout`,
`write_timeout` and `close_timeout`, and size/count limits for messages,
receive queues and pending sends. `send_ack/2` reports local transport
completion for each admitted frame. `send/2` reports admission only.

The client validates status 101, Upgrade/Connection tokens, exactly one
matching Sec-WebSocket-Accept and the selected subprotocol. Bytes coalesced
after the response head enter the frame parser. Redirects and uncertain
operations are never replayed. Ping is answered with Pong automatically;
Close is exchanged with the peer. This interface proxies WebSocket messages,
not raw TCP bytes or exact frame fragmentation. Forward binary messages
with `array_buffer/1` and configure the upstream protocols explicitly.

WSS retains the selected TLS backend's CA, hostname and SNI verification;
supply trusted `ssl: [cacertfile: ...]` for private CAs. Owner death closes
the connection. `close/1` signals cancellation; monitor the socket's public
`pid` for `:DOWN` to confirm cleanup, including a cancelled opening. A
Close event describes the handshake result; it does not prove peer
application receipt of earlier writes. Terminal cancellation discards
pending transport bytes, so a failed write may have reached the peer partly.

Plain Elixir binaries are sent as text frames. Use `array_buffer/1` or
`HTTP.Blob` for binary frames.

For `wss` connections, pass `tls_backend: :ssl | :ex_ssl` (or the equivalent
string in a map). When omitted, the shared `:http_core` TLS backend setting is
captured when the socket is created.

# `t`

```elixir
@type t() :: %HTTP.WebSocket{
  pid: pid() | nil,
  ref: reference() | nil,
  url: String.t() | nil
}
```

# `acknowledge`

```elixir
@spec acknowledge(t(), reference()) :: :ok | {:error, term()}
```

Acknowledges an ACK-mode message delivery reference.

# `array_buffer`

```elixir
@spec array_buffer(binary()) ::
  HTTP.WebSocket.ArrayBuffer.t() | {:error, :invalid_array_buffer}
```

# `binary_type`

```elixir
@spec binary_type(t()) :: :blob | :array_buffer
```

# `buffered_amount`

```elixir
@spec buffered_amount(t()) :: non_neg_integer()
```

# `close`

```elixir
@spec close(t()) :: :ok | {:error, term()}
```

# `close`

```elixir
@spec close(t(), non_neg_integer()) :: :ok | {:error, term()}
```

# `close`

```elixir
@spec close(t(), non_neg_integer() | nil, String.t()) :: :ok | {:error, term()}
```

# `closed`

```elixir
@spec closed() :: 3
```

# `closing`

```elixir
@spec closing() :: 2
```

# `connecting`

```elixir
@spec connecting() :: 0
```

# `extensions`

```elixir
@spec extensions(t()) :: String.t()
```

# `http_version`

```elixir
@spec http_version(t()) :: :http1 | :http2 | nil
```

Returns the negotiated HTTP version, or nil before establishment.

# `new`

```elixir
@spec new(String.t() | URI.t(), String.t() | [String.t()], keyword() | map()) ::
  t() | {:error, term()}
```

# `open`

```elixir
@spec open() :: 1
```

# `protocol`

```elixir
@spec protocol(t()) :: String.t()
```

# `ready_state`

```elixir
@spec ready_state(t()) :: 0 | 1 | 2 | 3
```

# `send`

```elixir
@spec send(t(), String.t() | HTTP.Blob.t() | HTTP.WebSocket.ArrayBuffer.t()) ::
  :ok | {:error, term()}
```

# `send_ack`

```elixir
@spec send_ack(t(), String.t() | HTTP.Blob.t() | HTTP.WebSocket.ArrayBuffer.t()) ::
  {:ok, reference()} | {:error, term()}
```

Admits a bounded send and returns its completion reference.

The owner receives `{HTTP.WebSocket, socket, {:send_result, ref, result}}`,
where `result` is `:ok` only after the complete frame was accepted by the
transport, or `{:error, reason}` on cancellation/failure. This confirms local
write completion, not peer application receipt. Failed/uncertain writes are
never replayed. Unlike `send/2`, a closed connection rejects admission.
Queuing and writing share the finite `write_timeout` (default 5 seconds).

# `send_frame`

```elixir
@spec send_frame(t(), {:text | :binary | :ping | :pong, binary()}) ::
  :ok | {:error, term()}
```

Admits an explicit text, binary, ping or pong frame in `mode: :proxy`.

Control payloads are limited to 125 bytes; text must be valid UTF-8. Frames
share the bounded FIFO send queue and write deadline with `send/2`. This
reports admission only; use `send_frame_ack/2` before forwarding Close.
Send Close using `close/3`, preserving the connection's close deadline.

# `send_frame_ack`

```elixir
@spec send_frame_ack(t(), {:text | :binary | :ping | :pong, binary()}) ::
  {:ok, reference()} | {:error, term()}
```

Admits an explicit proxy frame and returns a local write completion reference.

The owner receives the same `{:send_result, ref, result}` event as `send_ack/2`.
Wait for `:ok` for every admitted frame before calling `close/3` to preserve
prior writes. Completion does not prove peer application receipt.

# `set_binary_type`

```elixir
@spec set_binary_type(t(), :blob | :array_buffer) :: :ok | {:error, term()}
```

# `status`

```elixir
@spec status(t()) :: map()
```

Returns connection state and bounded delivery and send queue usage.

# `url`

```elixir
@spec url(t()) :: String.t() | nil
```

---

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