### Start WebSockex Client with start_link/4 Source: https://context7.com/witchtails/websockex_wt/llms.txt Demonstrates starting a WebSockex client process linked to the current process. It includes examples of handling connection and text frames, and starting with various options like authentication headers and timeouts. ```elixir defmodule MyClient do use WebSockex require Logger def start_link(url, state, opts \ []) do WebSockex.start_link(url, __MODULE__, state, opts) end def handle_connect(conn, state) do Logger.info("Connected to #{conn.host}:#{conn.port}") {:ok, state} end def handle_frame({:text, msg}, state) do Logger.info("Received: #{msg}") {:ok, state} end end # Basic usage {:ok, pid} = MyClient.start_link("ws://localhost:4000/socket", %{user_id: 123}) # With options {:ok, pid} = MyClient.start_link( "wss://secure.example.com/ws", %{}, [ name: MyClient, # Register with local name async: true, # Don't block on connection handle_initial_conn_failure: true, # Call handle_disconnect on initial failure extra_headers: [{"Authorization", "Bearer token123"}], socket_connect_timeout: 10_000, # 10 second TCP timeout socket_recv_timeout: 5_000 # 5 second HTTP response timeout ] ) # Returns {:ok, pid} on success, {:error, reason} on failure ``` -------------------------------- ### Manage Connection Establishment with handle_connect/2 Source: https://context7.com/witchtails/websockex_wt/llms.txt Shows how to use the `handle_connect/2` callback, which is invoked after a successful WebSocket connection or reconnection. It receives connection details and state, allowing for initial setup like authentication or subscriptions. This example demonstrates sending an authentication message after connection using `send/2` and `handle_info/2`. ```elixir defmodule AuthenticatedClient do use WebSockex require Logger def start_link(url, token) do WebSockex.start_link(url, __MODULE__, %{token: token, authenticated: false}) end def handle_connect(conn, state) do Logger.info("Connected to #{conn.host}") Logger.debug("Response headers: #{inspect(conn.resp_headers)}") # Perform authentication after connection auth_frame = {:text, Jason.encode!(%{action: "authenticate", token: state.token})} # Note: Can't use send_frame here, must return {:reply, ...} or send via cast # Schedule authentication via self-message send(self(), :authenticate) {:ok, state} end def handle_info(:authenticate, state) do frame = {:text, Jason.encode!(%{action: "auth", token: state.token})} {:reply, frame, state} end def handle_frame({:text, msg}, state) do case Jason.decode(msg) do {:ok, %{"type" => "auth_success"}} -> Logger.info("Authentication successful") {:ok, %{state | authenticated: true}} {:ok, %{"type" => "auth_failure"}} -> Logger.error("Authentication failed") {:close, {4001, "Authentication failed"}, state} _ -> {:ok, state} end end end ``` -------------------------------- ### Starting WebSockex Processes in Elixir (Linked and Unlinked) Source: https://github.com/witchtails/websockex_wt/blob/master/README.md Demonstrates the two primary methods for starting WebSockex processes in Elixir: `start_link/3` for linked processes (recommended for supervision) and `start/3` for unlinked (detached) processes. Using `start_link/3` is advised to prevent zombie processes. ```elixir iex> {:ok, pid} = WebSockex.start_link(url, __MODULE__, state) iex> {:ok, pid} = WebSockex.start(url, __MODULE__, state) ``` -------------------------------- ### Start Detached WebSockex Client with start/4 Source: https://context7.com/witchtails/websockex_wt/llms.txt Illustrates starting a WebSockex process that is not linked to the calling process. This is useful for detached operations, though `start_link/4` is generally preferred for integration with supervision trees. ```elixir defmodule DetachedClient do use WebSockex def start(url, state) do WebSockex.start(url, __MODULE__, state) end def handle_frame({:text, msg}, state) do IO.puts("Message: #{msg}") {:ok, state} end end # Start without linking {:ok, pid} = DetachedClient.start("ws://localhost:4000/socket", %{}) # Process continues running even if caller crashes ``` -------------------------------- ### WebSockex.start/4 Source: https://context7.com/witchtails/websockex_wt/llms.txt Starts a WebSockex process without linking to the current process. Use this when you need a detached process, though `start_link/4` is generally recommended to avoid orphaned processes outside supervision trees. ```APIDOC ## WebSockex.start/4 ### Description Starts a WebSockex process without linking to the current process. Use this when you need a detached process, though `start_link/4` is generally recommended to avoid orphaned processes outside supervision trees. ### Method `start` ### Endpoint N/A (Function call) ### Parameters #### Arguments - **url** (String.t | WebSockex.Conn.t) - The WebSocket URL or a connection struct. - **callback_module** (module) - The module implementing the WebSockex behaviour. - **initial_state** (any) - The initial state passed to the callback module. - **opts** (keyword) - Options for the connection (same as `start_link/4`). ### Request Example ```elixir defmodule DetachedClient do use WebSockex def start(url, state) do WebSockex.start(url, __MODULE__, state) end def handle_frame({:text, msg}, state) do IO.puts("Message: #{msg}") {:ok, state} end end # Start without linking {:ok, pid} = DetachedClient.start("ws://localhost:4000/socket", %{}) ``` ### Response #### Success Response - **{:ok, pid}** (tuple) - Returns the PID of the started WebSockex process on success. #### Error Response - **{:error, reason}** (tuple) - Returns an error reason if the connection fails. #### Response Example ```json {:ok, #PID<0.123.456>} ``` ``` -------------------------------- ### WebSockex.start_link/4 Source: https://context7.com/witchtails/websockex_wt/llms.txt Starts a WebSockex process linked to the current process. This is the primary way to create a WebSocket client. It accepts a URL string or a `WebSockex.Conn` struct, a callback module, initial state, and options. The process will establish a connection and invoke `handle_connect/2` upon success. ```APIDOC ## WebSockex.start_link/4 ### Description Starts a WebSockex process linked to the current process. This is the primary way to create a WebSocket client. It accepts a URL string or a `WebSockex.Conn` struct, a callback module, initial state, and options. The process will establish a connection and invoke `handle_connect/2` upon success. ### Method `start_link` ### Endpoint N/A (Function call) ### Parameters #### Arguments - **url** (String.t | WebSockex.Conn.t) - The WebSocket URL or a connection struct. - **callback_module** (module) - The module implementing the WebSockex behaviour. - **initial_state** (any) - The initial state passed to the callback module. - **opts** (keyword) - Options for the connection. Common options include: - `:name` (atom) - Register the process with a local name. - `:async` (boolean) - If true, the connection attempt is asynchronous. - `:handle_initial_conn_failure` (boolean) - If true, `handle_disconnect/2` is called on initial connection failure. - `:extra_headers` (list of tuples) - Custom headers to send with the HTTP request. - `:socket_connect_timeout` (integer) - TCP connection timeout in milliseconds. - `:socket_recv_timeout` (integer) - HTTP response timeout in milliseconds. ### Request Example ```elixir defmodule MyClient do use WebSockex require Logger def start_link(url, state, opts \ []) do WebSockex.start_link(url, __MODULE__, state, opts) end def handle_connect(conn, state) do Logger.info("Connected to #{conn.host}:#{conn.port}") {:ok, state} end def handle_frame({:text, msg}, state) do Logger.info("Received: #{msg}") {:ok, state} end end # Basic usage {:ok, pid} = MyClient.start_link("ws://localhost:4000/socket", %{user_id: 123}) # With options {:ok, pid} = MyClient.start_link( "wss://secure.example.com/ws", %{}, [ name: MyClient, async: true, handle_initial_conn_failure: true, extra_headers: [{"Authorization", "Bearer token123"}], socket_connect_timeout: 10_000, socket_recv_timeout: 5_000 ] ) ``` ### Response #### Success Response - **{:ok, pid}** (tuple) - Returns the PID of the started WebSockex process on success. #### Error Response - **{:error, reason}** (tuple) - Returns an error reason if the connection fails. #### Response Example ```json {:ok, #PID<0.123.456>} ``` ``` -------------------------------- ### Enabling Debug Tracing After Process Start in Elixir Source: https://github.com/witchtails/websockex_wt/blob/master/README.md Illustrates how to enable debug tracing for an existing Websockex process using the `:sys.trace/2` function. This allows for dynamic debugging of a running process. ```elixir iex> {:ok, pid} = EchoClient.start_link() iex> :sys.trace(pid, true) :ok iex> EchoClient.echo(pid, "Hi") *DBG* #PID<0.379.0> sending frame: {:text, "Hi"} :ok *DBG* #PID<0.379.0> received frame: {:text, "Hi"} ``` -------------------------------- ### Send WebSocket Frames with send_frame/3 Source: https://context7.com/witchtails/websockex_wt/llms.txt Shows how to send various types of WebSocket frames (text, binary, ping, pong) synchronously using `WebSockex.send_frame/3`. It includes examples for different frame types, custom timeouts, and error handling for disconnected or errored connections. ```elixir defmodule ChatClient do use WebSockex def start_link(url) do WebSockex.start_link(url, __MODULE__, %{messages: []}) end # Public API for sending messages def send_message(pid, message) do WebSockex.send_frame(pid, {:text, message}) end def send_binary(pid, data) do WebSockex.send_frame(pid, {:binary, data}) end def ping(pid) do WebSockex.send_frame(pid, :ping) end def ping_with_payload(pid, payload) do WebSockex.send_frame(pid, {:ping, payload}) end def handle_frame({:text, msg}, state) do {:ok, %{state | messages: [msg | state.messages]}} end end {:ok, pid} = ChatClient.start_link("ws://chat.example.com/ws") # Send text frame :ok = ChatClient.send_message(pid, "Hello, World!") # Send binary frame :ok = ChatClient.send_binary(pid, <<1, 2, 3, 4>>) # Send ping :ok = ChatClient.ping(pid) # With custom timeout (default 5000ms) :ok = WebSockex.send_frame(pid, {:text, "message"}, 10_000) # Error cases {:error, %WebSockex.NotConnectedError{}} = WebSockex.send_frame(pid, {:text, "msg"}) {:error, %WebSockex.ConnError{}} = WebSockex.send_frame(pid, {:text, "msg"}) ``` -------------------------------- ### Integrating WebSockex Client into Elixir Supervision Tree Source: https://github.com/witchtails/websockex_wt/blob/master/README.md Illustrates how to integrate a WebSockex client into an Elixir application's supervision tree using the `Supervisor.start_link/2` function. This allows the WebSocket connection to be managed and restarted by the supervisor. ```elixir defmodule MyApp.Client do use WebSockex def start_link(state) do WebSockex.start_link("ws://myapp.ninja", __MODULE__, state) end end defmodule MyApp.Application do use Application def start(_type, _args) do children = [ {MyApp.Client, ["WebSockex is Great"]} ] Supervisor.start_link(children, strategy: :one_for_one) end end ``` -------------------------------- ### Basic WebSockex Client Implementation in Elixir Source: https://github.com/witchtails/websockex_wt/blob/master/README.md Demonstrates a simple Elixir module implementing the WebSockex behavior to connect to a WebSocket URL, handle incoming frames, and send outgoing frames. It requires the `websockex` library as a dependency. ```elixir defmodule WebSocketExample do use WebSockex def start_link(url, state) do WebSockex.start_link(url, __MODULE__, state) end def handle_frame({type, msg}, state) do IO.puts "Received Message - Type: #{inspect type} -- Message: #{inspect msg}" {:ok, state} end def handle_cast({:send, {type, msg} = frame}, state) do IO.puts "Sending #{type} frame with payload: #{msg}" {:reply, frame, state} end end ``` -------------------------------- ### Enabling Debug Tracing with Websockex in Elixir Source: https://github.com/witchtails/websockex_wt/blob/master/README.md Shows how to enable debug tracing for Websockex clients during startup using the `debug: [:trace]` option. This provides detailed logs of connection, frame sending, and receiving events. ```elixir iex> {:ok, pid} = EchoClient.start_link(debug: [:trace]) *DBG* #PID<0.371.0> attempting to connect *DBG* #PID<0.371.0> sucessfully connected {:ok, #PID<0.371.0>} iex> EchoClient.echo(pid, "Hello") *DBG* #PID<0.371.0> sending frame: {:text, "Hello"} :ok *DBG* #PID<0.371.0> received frame: {:text, "Hello"} *DBG* #PID<0.371.0> received frame: :ping *DBG* #PID<0.371.0> replying from :handle_ping with :pong iex> EchoClient.echo(pid, "Close the things!") *DBG* #PID<0.371.0> sending frame: {:text, "Close the things!"} :ok *DBG* #PID<0.371.0> received frame: {:text, "Close the things!"} *DBG* #PID<0.371.0> closing with local reason: {:local, :normal} *DBG* #PID<0.371.0> sending close frame: {:local, :normal} *DBG* #PID<0.371.0> forcefully closed the connection because the server was taking too long close ``` -------------------------------- ### Create WebSockex Connection with Custom Options Source: https://context7.com/witchtails/websockex_wt/llms.txt Demonstrates creating a WebSockex connection with custom headers, timeouts, and SSL options. It also shows how to access server response headers after a successful connection. ```elixir defmodule CustomConnection do use WebSockex require Logger def start_link(url, opts \ []) do # Create connection with custom options conn = WebSockex.Conn.new(url, [ extra_headers: [ {"Authorization", "Bearer " <> opts[:token]}, {"X-Client-Version", "1.0.0"} ], socket_connect_timeout: 10_000, socket_recv_timeout: 5_000, insecure: false, # Enable SSL verification ssl_options: [ verify: :verify_peer, cacertfile: "/path/to/ca-bundle.crt" ] ]) WebSockex.start_link(conn, __MODULE__, opts[:state] || %{}) end def handle_connect(conn, state) do # Access response headers Logger.info("Response headers: #{inspect(conn.resp_headers)}") # Check for specific header case List.keyfind(conn.resp_headers, "X-Session-Id", 0) do {_, session_id} -> Logger.info("Session ID: #{session_id}") {:ok, Map.put(state, :session_id, session_id)} nil -> {:ok, state} end end def handle_frame({:text, msg}, state), do: {:ok, state} end # Parse URL manually {:ok, uri} = WebSockex.Conn.parse_url("wss://api.example.com/ws?token=abc") # => {:ok, %URI{host: "api.example.com", port: 443, path: "/ws", query: "token=abc", scheme: "wss"}} ``` -------------------------------- ### Integrate WebSockex with OTP Supervision Trees Source: https://context7.com/witchtails/websockex_wt/llms.txt Shows how to integrate WebSockex processes into an OTP supervision tree using `child_spec/1` or `child_spec/2`. Supports Elixir 1.5+ supervisor child specification format. ```elixir defmodule MyApp.WebSocketClient do use WebSockex def start_link(state) do WebSockex.start_link("wss://api.example.com/ws", __MODULE__, state, name: __MODULE__) end def handle_frame({:text, msg}, state), do: {:ok, state} end defmodule MyApp.Application do use Application def start(_type, _args) do children = [ # Simple supervision - uses child_spec/1 {MyApp.WebSocketClient, %{user: "default"}}, # Or with explicit spec %{ id: MyApp.WebSocketClient, start: {MyApp.WebSocketClient, :start_link, [%{user: "default"}]}, restart: :permanent, shutdown: 5000, type: :worker } ] opts = [strategy: :one_for_one, name: MyApp.Supervisor] Supervisor.start_link(children, opts) end end # Dynamic supervision defmodule MyApp.ClientSupervisor do use DynamicSupervisor def start_link(init_arg) do DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__) end def init(_init_arg) do DynamicSupervisor.init(strategy: :one_for_one) end def start_client(user_id) do spec = {MyApp.WebSocketClient, %{user_id: user_id}} DynamicSupervisor.start_child(__MODULE__, spec) end end ``` -------------------------------- ### Adding WebSockex to Elixir Project Dependencies Source: https://github.com/witchtails/websockex_wt/blob/master/README.md Shows how to add the `websockex` library to the `deps` function in an Elixir project's `mix.exs` file. This ensures the library is available for use in the project. ```elixir def deps do [{:websockex, "~> 0.5.1"}] end ``` -------------------------------- ### Handle Incoming WebSocket Frames with handle_frame/2 Source: https://context7.com/witchtails/websockex_wt/llms.txt Illustrates how to process incoming WebSocket frames (text, binary, control) using the `handle_frame/2` callback. This function allows for parsing JSON, responding to specific message types like 'ping' or 'shutdown', logging, and updating state. It depends on `WebSockex` and `Jason` for JSON handling, and `Logger` for output. ```elixir defmodule MessageHandler do use WebSockex require Logger def start_link(url) do WebSockex.start_link(url, __MODULE__, %{message_count: 0}) end # Handle text frames def handle_frame({:text, msg}, state) do case Jason.decode(msg) do {:ok, %{"type" => "ping"}} -> # Reply with pong {:reply, {:text, Jason.encode!(%{type: "pong"})}, state} {:ok, %{"type" => "shutdown"}} -> # Close the connection gracefully {:close, state} {:ok, %{"type" => "error", "code" => code}} -> # Close with custom code and reason {:close, {4000, "Application error: #{code}"}, state} {:ok, data} -> Logger.info("Received data: #{inspect(data)}") {:ok, %{state | message_count: state.message_count + 1}} {:error, _} -> Logger.warning("Invalid JSON received: #{msg}") {:ok, state} end end # Handle binary frames def handle_frame({:binary, data}, state) do Logger.info("Received #{byte_size(data)} bytes of binary data") {:ok, state} end end {:ok, pid} = MessageHandler.start_link("ws://api.example.com/ws") # Frames are automatically routed to handle_frame/2 ``` -------------------------------- ### Basic WebSocket Connection Closure in Elixir with WebSockex Source: https://github.com/witchtails/websockex_wt/blob/master/README.md Shows how to initiate a basic WebSocket connection closure in Elixir by returning `{:close, state}` from various WebSockex callback functions. This closes the connection with the default status code 1000 (normal closure). ```elixir def handle_frame({:text, "shutdown"}, state) do {:close, state} end def handle_cast(:close, state) do {:close, state} end ``` -------------------------------- ### Debugging WebSockex with :sys Module Source: https://context7.com/witchtails/websockex_wt/llms.txt Explains how to use Elixir's built-in `:sys` module for debugging WebSockex connections, including enabling tracing, collecting statistics, and inspecting/modifying process state. ```elixir defmodule DebugClient do use WebSockex def start_link(url, opts \ []) do # Enable tracing at startup WebSockex.start_link(url, __MODULE__, %{}, debug: [:trace] ++ opts) end def handle_frame({:text, msg}, state), do: {:ok, state} end # Start with tracing enabled {:ok, pid} = DebugClient.start_link("wss://echo.websocket.events") # Output: # *DBG* #PID<0.123.0> attempting to connect # *DBG* #PID<0.123.0> successfully connected # Enable tracing on running process {:ok, pid} = DebugClient.start_link("wss://echo.websocket.events", []) :sys.trace(pid, true) # Other debug options :sys.trace(pid, false) # Disable tracing :sys.statistics(pid, true) # Enable statistics :sys.get_state(pid) # Get current state {:ok, state} = :sys.get_state(pid) :sys.replace_state(pid, fn state -> # Replace state Map.put(state, :debug_mode, true) end) :sys.get_status(pid) # Get full status # Debug options available at startup: # - :trace - Print all events # - :log - Store events in log # - {:log, N} - Store last N events # - :statistics - Enable statistics collection # - {:log_to_file, path} - Log to file ``` -------------------------------- ### Implement Heartbeat Monitoring with Ping/Pong Callbacks Source: https://context7.com/witchtails/websockex_wt/llms.txt This Elixir code demonstrates custom handling of WebSocket ping and pong frames using `handle_ping/2` and `handle_pong/2` callbacks in WebSockex. It allows for latency monitoring by sending timestamps with pings and calculating the round-trip time upon receiving pongs. ```elixir defmodule HeartbeatClient do use WebSockex require Logger def start_link(url) do WebSockex.start_link(url, __MODULE__, %{last_pong: nil, latency: nil}) end # Custom ping handler - respond with pong containing same payload def handle_ping(:ping, state) do Logger.debug("Received ping") {:reply, :pong, state} end def handle_ping({:ping, payload}, state) do Logger.debug("Received ping with payload: #{inspect(payload)}") {:reply, {:pong, payload}, state} end # Track pong responses for latency monitoring def handle_pong(:pong, state) do Logger.debug("Received pong") {:ok, %{state | last_pong: System.monotonic_time(:millisecond)}} end def handle_pong({:pong, timestamp_payload}, state) do case Integer.parse(timestamp_payload) do {sent_time, _} -> latency = System.monotonic_time(:millisecond) - sent_time Logger.info("Latency: #{latency}ms") {:ok, %{state | latency: latency, last_pong: System.monotonic_time(:millisecond)}} :error -> {:ok, state} end end def handle_frame({:text, msg}, state), do: {:ok, state} # Send periodic pings with timestamps def handle_info(:send_heartbeat, state) do timestamp = Integer.to_string(System.monotonic_time(:millisecond)) {:reply, {:ping, timestamp}, state} end end ``` -------------------------------- ### Send Asynchronous Messages with WebSockex.cast/2 Source: https://context7.com/witchtails/websockex_wt/llms.txt Demonstrates how to asynchronously send messages to a WebSockex process using the `cast/2` function. This method is suitable for triggering frame sends and allows for internal batching and flow control. It requires the `WebSockex` module and `Jason` for JSON encoding. ```elixir defmodule StreamClient do use WebSockex def start_link(url) do WebSockex.start_link(url, __MODULE__, %{}) end # Public API using cast def subscribe(pid, channel) do WebSockex.cast(pid, {:subscribe, channel}) end def unsubscribe(pid, channel) do WebSockex.cast(pid, {:unsubscribe, channel}) end def broadcast(pid, channel, message) do WebSockex.cast(pid, {:broadcast, channel, message}) end # Handle cast messages def handle_cast({:subscribe, channel}, state) do frame = {:text, Jason.encode!(%{action: "subscribe", channel: channel})} {:reply, frame, state} end def handle_cast({:unsubscribe, channel}, state) do frame = {:text, Jason.encode!(%{action: "unsubscribe", channel: channel})} {:reply, frame, state} end def handle_cast({:broadcast, channel, message}, state) do frame = {:text, Jason.encode!(%{action: "broadcast", channel: channel, data: message})} {:reply, frame, state} end def handle_frame({:text, msg}, state) do IO.puts("Received: #{msg}") {:ok, state} end end {:ok, pid} = StreamClient.start_link("ws://stream.example.com/ws") :ok = StreamClient.subscribe(pid, "events") :ok = StreamClient.broadcast(pid, "events", %{type: "notification", body: "Hello"}) ``` -------------------------------- ### Handle External and Scheduled Messages with handle_info/2 Source: https://context7.com/witchtails/websockex_wt/llms.txt This Elixir code implements the `handle_info/2` callback in WebSockex to process messages from other processes, timers, and self-sent messages. It demonstrates scheduling periodic heartbeats, sending data to the server, and flushing queued messages in batches. ```elixir defmodule ScheduledClient do use WebSockex require Logger def start_link(url) do {:ok, pid} = WebSockex.start_link(url, __MODULE__, %{queue: []}) schedule_heartbeat(pid) {:ok, pid} end defp schedule_heartbeat(pid) do Process.send_after(pid, :heartbeat, 30_000) end # Handle timer messages def handle_info(:heartbeat, state) do Logger.debug("Sending heartbeat") schedule_heartbeat(self()) {:reply, {:text, Jason.encode!(%{type: "heartbeat"})}, state} end # Handle messages from other processes def handle_info({:send_to_server, data}, state) do frame = {:text, Jason.encode!(data)} {:reply, frame, state} end # Handle queued messages def handle_info(:flush_queue, %{queue: queue} = state) when queue != [] do batch = %{type: "batch", messages: Enum.reverse(queue)} {:reply, {:text, Jason.encode!(batch)}, %{state | queue: []}} end def handle_info(:flush_queue, state), do: {:ok, state} def handle_frame({:text, msg}, state), do: {:ok, state} end {:ok, pid} = ScheduledClient.start_link("ws://example.com/ws") # Send from another process send(pid, {:send_to_server, %{action: "update", value: 42}}) ``` -------------------------------- ### Closing WebSocket Connection with Custom Code and Reason in Elixir Source: https://github.com/witchtails/websockex_wt/blob/master/README.md Demonstrates how to close a WebSocket connection with a custom status code and reason message in Elixir using WebSockex. This is achieved by returning `{:close, {close_code, message}, state}` from a callback. ```elixir def handle_frame({:text, "error"}, state) do {:close, {4000, "Custom application error"}, state} end ``` -------------------------------- ### Custom Termination Callback in Elixir Source: https://github.com/witchtails/websockex_wt/blob/master/README.md Demonstrates how to define a `terminate` callback in Elixir to exit a socket process normally, even after an exceptional close or error. This is useful for handling abrupt closures gracefully. ```elixir def terminate(reason, state) do IO.puts("\nSocket Terminating:\n#{inspect reason}\n\n#{inspect state}\n") exit(:normal) end ``` -------------------------------- ### Elixir Telemetry Events for WebSockex Source: https://context7.com/witchtails/websockex_wt/llms.txt This Elixir code sets up telemetry event handlers for various WebSockex events such as connection, disconnection, frame sending/receiving, and termination. It logs these events with relevant metadata like connection details and the callback module. Ensure the `:telemetry` library is available. ```elixir defmodule MyApp.WebSocketTelemetry do require Logger def setup do events = [ [:websockex, :connected], [:websockex, :disconnected], [:websockex, :frame, :received], [:websockex, :frame, :sent], [:websockex, :terminate] ] :telemetry.attach_many( "websocket-handler", events, &handle_event/4, nil ) end def handle_event([:websockex, :connected], %{time: time}, %{conn: conn, module: module}, _config) do Logger.info("[#{module}] Connected to #{conn.host}:#{conn.port}") end def handle_event([:websockex, :disconnected], %{time: time}, metadata, _config) do %{conn: conn, module: module, reason: reason} = metadata Logger.warning("[#{module}] Disconnected from #{conn.host}: #{inspect(reason)}") end def handle_event([:websockex, :frame, :received], %{time: time}, %{frame: frame, module: module}, _config) do Logger.debug("[#{module}] Received frame: #{inspect(frame)}") end def handle_event([:websockex, :frame, :sent], %{time: time}, %{frame: frame, module: module}, _config) do Logger.debug("[#{module}] Sent frame: #{inspect(frame)}") end def handle_event([:websockex, :terminate], %{time: time}, %{reason: reason, module: module}, _config) do Logger.info("[#{module}] Terminated: #{inspect(reason)}") end end # In application startup MyApp.WebSocketTelemetry.setup() ``` -------------------------------- ### Elixir WebSockex Graceful Connection Closing Source: https://context7.com/witchtails/websockex_wt/llms.txt This Elixir code demonstrates how to gracefully close WebSocket connections using WebSockex. It shows closing from callbacks by returning specific tuples and programmatically via cast messages, including options for custom close codes and reason messages. This requires the WebSockex library. ```elixir defmodule GracefulClient do use WebSockex require Logger def start_link(url) do WebSockex.start_link(url, __MODULE__, %{}) end # Public API for closing def close(pid), do: WebSockex.cast(pid, :close) def close(pid, code, reason), do: WebSockex.cast(pid, {:close, code, reason}) # Close from callback - basic def handle_frame({:text, "quit"}, state) do Logger.info("Received quit command, closing connection") {:close, state} end # Close with custom code and reason def handle_frame({:text, "error:" <> error_msg}, state) do Logger.error("Server error: #{error_msg}") {:close, {4000, "Client received error: #{error_msg}"}, state} end # Close via cast def handle_cast(:close, state) do Logger.info("Close requested via cast") {:close, state} end def handle_cast({:close, code, reason}, state) do Logger.info("Close requested with code #{code}: #{reason}") {:close, {code, reason}, state} end def handle_frame({:text, msg}, state), do: {:ok, state} # Handle disconnect after close def handle_disconnect(%{reason: {:local, :normal}}, state) do Logger.info("Connection closed normally") {:ok, state} end def handle_disconnect(%{reason: {:local, code, reason}}, state) do Logger.info("Connection closed with code #{code}: #{reason}") {:ok, state} end # Custom terminate behavior def terminate(reason, state) do Logger.info("Client terminating: #{inspect(reason)}") :ok end end # Close codes: # 1000 - Normal closure (default) # 1001 - Going away # 1002 - Protocol error # 1003 - Unsupported data # 3000-3999 - Library/framework use # 4000-4999 - Application use ``` -------------------------------- ### WebSockex.send_frame/3 Source: https://context7.com/witchtails/websockex_wt/llms.txt Synchronously sends a WebSocket frame through the connection. Supports text, binary, ping, and pong frames. Returns `:ok` on success or an error tuple if the connection is not established or an encoding error occurs. This function cannot be called from within a callback - use `{:reply, frame, state}` return values instead. ```APIDOC ## WebSockex.send_frame/3 ### Description Synchronously sends a WebSocket frame through the connection. Supports text, binary, ping, and pong frames. Returns `:ok` on success or an error tuple if the connection is not established or an encoding error occurs. This function cannot be called from within a callback - use `{:reply, frame, state}` return values instead. ### Method `send_frame` ### Endpoint N/A (Function call) ### Parameters #### Arguments - **pid** (pid) - The PID of the WebSockex process. - **frame** (term) - The frame to send. Can be: - `{:text, String.t}` - `{:binary, binary()} - `:ping` - `{:ping, binary()} - `:pong` - `{:pong, binary()} - **timeout** (integer, optional) - The timeout in milliseconds for sending the frame. Defaults to 5000ms. ### Request Example ```elixir defmodule ChatClient do use WebSockex def start_link(url) do WebSockex.start_link(url, __MODULE__, %{messages: []}) end # Public API for sending messages def send_message(pid, message) do WebSockex.send_frame(pid, {:text, message}) end def send_binary(pid, data) do WebSockex.send_frame(pid, {:binary, data}) end def ping(pid) do WebSockex.send_frame(pid, :ping) end def ping_with_payload(pid, payload) do WebSockex.send_frame(pid, {:ping, payload}) end def handle_frame({:text, msg}, state) do {:ok, %{state | messages: [msg | state.messages]}} end end {:ok, pid} = ChatClient.start_link("ws://chat.example.com/ws") # Send text frame :ok = ChatClient.send_message(pid, "Hello, World!") # Send binary frame :ok = ChatClient.send_binary(pid, <<1, 2, 3, 4>>) # Send ping :ok = ChatClient.ping(pid) # With custom timeout (default 5000ms) :ok = WebSockex.send_frame(pid, {:text, "message"}, 10_000) ``` ### Response #### Success Response - **:ok** (atom) - Indicates the frame was sent successfully. #### Error Response - **{:error, %WebSockex.NotConnectedError{}}** - Returned if the connection is not established. - **{:error, %WebSockex.ConnError{}}** - Returned if an error occurs during connection or sending. #### Response Example ```json :ok ``` #### Error Example ```json {:error, %WebSockex.NotConnectedError{}} ``` ``` -------------------------------- ### Handle WebSocket Disconnects with Reconnection Logic Source: https://context7.com/witchtails/websockex_wt/llms.txt This Elixir code defines the `handle_disconnect/2` callback for WebSockex. It manages connection loss by implementing reconnection strategies with backoff delays and a maximum retry limit. It also includes logic to reconnect to a backup server during maintenance. ```elixir defmodule ResilientClient do use WebSockex require Logger @max_retries 5 @backoff_ms 1000 def start_link(url, opts \ []) do WebSockex.start_link(url, __MODULE__, %{url: url}, handle_initial_conn_failure: true, async: true ) end def handle_disconnect(%{reason: reason, attempt_number: attempt}, state) when attempt <= @max_retries do backoff = @backoff_ms * attempt Logger.warning("Disconnected: #{inspect(reason)}. Reconnecting in #{backoff}ms (attempt #{attempt}/#{@max_retries})") Process.sleep(backoff) {:reconnect, state} end def handle_disconnect(%{reason: reason, attempt_number: attempt}, state) do Logger.error("Max reconnection attempts (#{@max_retries}) reached. Giving up.") {:ok, state} end # Reconnect to a different URL if needed def handle_disconnect(%{reason: {:remote, 4001, "Server maintenance"}, conn: conn}, state) do Logger.info("Server under maintenance, connecting to backup") new_conn = WebSockex.Conn.new("wss://backup.example.com/ws") {:reconnect, new_conn, state} end def handle_frame({:text, msg}, state) do {:ok, state} end end ``` -------------------------------- ### Programmatic WebSocket Connection Closure via Cast in Elixir Source: https://github.com/witchtails/websockex_wt/blob/master/README.md Explains how to programmatically close a WebSocket connection from outside the WebSockex process by sending a `:close` cast message. The corresponding `handle_cast/2` callback then returns `{:close, state}` to initiate the closure. ```elixir # In your client module def close(pid) do WebSockex.cast(pid, :close) end def handle_cast(:close, state) do {:close, state} end ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.