{"id":32163894,"url":"https://github.com/gsmlg-dev/phoenix_socket_client","last_synced_at":"2026-02-20T00:31:47.212Z","repository":{"id":310031075,"uuid":"1038413760","full_name":"gsmlg-dev/phoenix_socket_client","owner":"gsmlg-dev","description":"Elixir Phoenix Client for Channels","archived":false,"fork":false,"pushed_at":"2025-10-30T18:56:31.000Z","size":219,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-30T19:30:50.246Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"mobileoverlord/phoenix_client","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gsmlg-dev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-15T06:41:20.000Z","updated_at":"2025-10-30T18:56:34.000Z","dependencies_parsed_at":"2025-09-03T10:20:01.948Z","dependency_job_id":"070bdbf6-18bf-4da6-8590-9140269ec25f","html_url":"https://github.com/gsmlg-dev/phoenix_socket_client","commit_stats":null,"previous_names":["gsmlg-dev/phoenix_client","gsmlg-dev/phoenix_socket_client"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/gsmlg-dev/phoenix_socket_client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fphoenix_socket_client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fphoenix_socket_client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fphoenix_socket_client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fphoenix_socket_client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gsmlg-dev","download_url":"https://codeload.github.com/gsmlg-dev/phoenix_socket_client/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gsmlg-dev%2Fphoenix_socket_client/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29637408,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T22:32:43.237Z","status":"ssl_error","status_checked_at":"2026-02-19T22:32:38.330Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-21T14:40:32.048Z","updated_at":"2026-02-20T00:31:47.206Z","avatar_url":"https://github.com/gsmlg-dev.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Phoenix.SocketClient\n\n[![release](https://github.com/gsmlg-dev/phoenix_socket_client/actions/workflows/release.yml/badge.svg)](https://github.com/gsmlg-dev/phoenix_socket_client/actions/workflows/release.yml)\n[![Hex.pm](https://img.shields.io/hexpm/v/phoenix_socket_client.svg)](https://hex.pm/packages/phoenix_socket_client)\n[![Documentation](https://img.shields.io/badge/documentation-v0.7.0-blue)](https://hexdocs.pm/phoenix_socket_client/0.7.0)\n\nElixir client for Phoenix Channels WebSocket connections.\n\n## Installation\n\nAdd `phoenix_socket_client` to your list of dependencies in `mix.exs`.\nThe package is available on [Hex.pm](https://hex.pm/packages/phoenix_socket_client).\n\n```elixir\ndef deps do\n  [\n    {:phoenix_socket_client, \"~\u003e 0.7.0\"}\n  ]\nend\n```\n\n## Usage\n\n### Basic Connection\n\nThe socket can be started as a supervised process by adding it to your supervision tree.\nYou can also start it as a standalone process.\n\n```elixir\n# As a supervised process\ndef start(_type, _args) do\n  children = [\n    {Phoenix.SocketClient, url: \"ws://localhost:4000/socket/websocket\", name: MyApp.Socket}\n  ]\n\n  Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)\nend\n\n# As a standalone process\n{:ok, socket} = Phoenix.SocketClient.start_link(\n  url: \"ws://localhost:4000/socket/websocket\",\n  params: %{\"token\" =\u003e \"your-token\"},\n  headers: [{\"Authorization\", \"Bearer your-token\"}]\n)\n```\n\n### Joining Channels\n\n```elixir\n{:ok, response, channel} = Phoenix.SocketClient.Channel.join(socket, \"rooms:lobby\", %{user_id: 123})\n\n# Handle incoming messages via message passing\n# Messages are received as Phoenix.SocketClient.Message structs\nreceive do\n  %Phoenix.SocketClient.Message{event: \"new_msg\", payload: payload} -\u003e\n    IO.inspect(payload)\n  %Phoenix.SocketClient.Message{event: \"user:joined\", payload: payload} -\u003e\n    IO.puts(\"User joined: #{inspect(payload)}\")\nend\n\n# Or in tests/explicit handling:\nassert_receive %Phoenix.SocketClient.Message{event: \"new_msg\", payload: %{\"body\" =\u003e body}}\n\n# Push messages to the channel\nPhoenix.SocketClient.Channel.push(channel, \"new_msg\", %{\"body\" =\u003e \"Hello\"})\n```\n\n### Custom Channels\n\nYou can create your own channel modules to handle channel logic in a more structured way.\nThe easiest way to create a custom channel is to `use Phoenix.SocketClient.Channel`:\n\n```elixir\ndefmodule MyChannel do\n  use Phoenix.SocketClient.Channel\n\n  @impl true\n  def init(args) do\n    # initialize your channel state\n    # args: {sup_pid, socket_pid, topic, params}\n    {:ok, args}\n  end\n\n  # optional callbacks\n  # @impl true\n  # def handle_in(event, payload, state) do\n  #   {:noreply, state}\n  # end\nend\n```\n\nThen, you can use the `topic_channel_map` option when starting the socket to map a topic to your custom channel:\n\n```elixir\n{:ok, socket} = Phoenix.SocketClient.start_link(\n  url: \"ws://localhost:4000/socket/websocket\",\n  topic_channel_map: %{\n    \"rooms:lobby\" =\u003e MyChannel\n  }\n)\n```\n\n### Handling Incoming Messages with Hooks\n\nYou can also handle incoming messages using hooks. This is useful when you want to handle messages in a more direct way, without using message passing.\n\n```elixir\n{:ok, response, channel} = Phoenix.SocketClient.Channel.join(socket, \"rooms:lobby\")\n\n# Register a hook for the \"new_msg\" event\nPhoenix.SocketClient.Channel.on(channel, \"new_msg\", fn payload -\u003e\n  IO.inspect(payload)\nend)\n\n# Unregister the hook\nPhoenix.SocketClient.Channel.off(channel, \"new_msg\")\n```\n\nYou can also use a module as a hook. The module must implement a `handle_in/2` function, which will be called with the event and the payload.\n\n```elixir\ndefmodule MyHook do\n  def handle_in(event, payload) do\n    IO.puts(\"Got event \\#{event} with payload \\#{inspect payload}\")\n  end\nend\n\n{:ok, response, channel} = Phoenix.SocketClient.Channel.join(socket, \"rooms:lobby\")\n\n# Register a module hook for the \"new_msg\" event\nPhoenix.SocketClient.Channel.on(channel, \"new_msg\", MyHook)\n```\n\n### Reconfiguring the Socket\n\nYou can reconfigure the socket client at runtime using the `reconfigure/2` function.\nIf any connection-related options are changed, the socket will be restarted.\n\n```elixir\n# Change the url\nPhoenix.SocketClient.reconfigure(socket, url: \"ws://new.example.com/socket\")\n\n# Change the connection params\nPhoenix.SocketClient.reconfigure(socket, params: %{\"token\" =\u003e \"new-token\"})\n```\n\n### Configuration Options\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `:name` | `atom()` | `nil` | The name to register the socket supervisor. |\n| `:url` | `String.t()` | **Required** | WebSocket URL (e.g., \"ws://localhost:4000/socket/websocket\") |\n| `:params` | `map() \\| keyword()` | `%{}` | Query parameters for connection |\n| `:headers` | `[{String.t(), String.t()}]` | `[]` | HTTP headers for connection |\n| `:transport` | `module()` | `Phoenix.SocketClient.Transports.Websocket` | Transport module |\n| `:heartbeat_interval` | `integer()` | `30_000` | Keep-alive interval in milliseconds |\n| `:reconnect_interval` | `integer()` | `60_000` | Reconnection delay in milliseconds |\n| `:reconnect?` | `boolean()` | `true` | Enable automatic reconnection |\n| `:auto_connect` | `boolean()` | `true` | Connect automatically on startup |\n| `:serializer` | `module()` | `Jason` | JSON serializer module |\n| `:vsn` | `String.t()` | `\"2.0.0\"` | Phoenix Channels protocol version |\n| `:topic_channel_map` | `map()` | `%_` | A map from a topic string to a channel module. |\n| `:join_channels` | `list()` | `[]` | A list of channels to automatically join on connect. |\n| `:default_channel_module` | `module()` | `Phoenix.SocketClient.Channel.EchoRoom` | The default channel module to use. |\n| `:default_channel_params` | `map()` | `%_` | The default parameters to use for channels. |\n\n### Message Handling Examples\n\n```elixir\n# Using in a GenServer or other process\n{:ok, response, channel} = Phoenix.SocketClient.Channel.join(socket, \"rooms:lobby\")\n\n# In your GenServer handle_info or process loop:\ndef handle_info(%Phoenix.SocketClient.Message{event: \"new_msg\", payload: payload}, state) do\n  IO.puts(\"New message: #{inspect(payload)}\")\n  {:noreply, state}\nend\n\ndef handle_info(%Phoenix.SocketClient.Message{event: \"user:joined\", payload: %{\"user\" =\u003e user}}, state) do\n  IO.puts(\"User #{user} joined the room\")\n  {:noreply, state}\nend\n\n# For simple usage, use receive blocks:\nreceive do\n  %Phoenix.SocketClient.Message{event: event, payload: payload} -\u003e\n    IO.puts(\"Received event: #{event} with payload: #{inspect(payload)}\")\nafter\n  5_000 -\u003e IO.puts(\"No messages received\")\nend\n```\n\n### Error Handling Examples\n\n```elixir\n# Handle connection errors\n{:ok, socket} = Phoenix.SocketClient.start_link(\n  url: \"ws://localhost:4000/socket/websocket\",\n  params: %{\"token\" =\u003e \"invalid-token\"}\n)\n\n# Check connection status\nif Phoenix.SocketClient.connected?(socket) do\n  {:ok, _response, channel} = Phoenix.SocketClient.Channel.join(socket, \"rooms:lobby\")\nelse\n  IO.puts(\"Failed to connect to server\")\nend\n\n# Handle channel join errors\ncase Phoenix.SocketClient.Channel.join(socket, \"rooms:private\", %{user_id: 123}) do\n  {:ok, response, channel} -\u003e\n    # Successfully joined channel\n    IO.inspect(response)\n\n  {:error, :timeout} -\u003e\n    # Join request timed out\n    IO.puts(\"Channel join timed out\")\n\n  {:error, :unauthorized} -\u003e\n    # Not authorized to join this channel\n    IO.puts(\"Not authorized to join channel\")\n\n  {:error, reason} -\u003e\n    # Other join errors\n    IO.puts(\"Failed to join channel: #{inspect(reason)}\")\nend\n\n# Handle message push errors\ncase Phoenix.SocketClient.Channel.push(channel, \"new_msg\", %{body: \"Hello\"}, 5000) do\n  {:ok, response} -\u003e\n    # Message sent successfully\n    IO.inspect(response)\n\n  {:error, :timeout} -\u003e\n    # Push request timed out\n    IO.puts(\"Message push timed out\")\n\n  {:error, :channel_closed} -\u003e\n    # Channel was closed\n    IO.puts(\"Channel is no longer available\")\nend\n```\n\n## Development\n\n### Setup\n\n```bash\n# Install dependencies\nmix deps.get\n\n# Compile\nmix compile\n\n# Run tests\nmix test\n\n# Format code\nmix format\n```\n\n### Requirements\n\n- Elixir 1.17+\n- Erlang/OTP 26+\n\n## Architecture\n\nThe client uses a supervisor tree with separate processes for:\n- Socket connection management\n- Channel lifecycle\n- Individual channel processes\n- State management\n\nAll processes are properly supervised with automatic restart strategies.\n\n## Telemetry\n\nThe library includes comprehensive telemetry events for monitoring and debugging.\nThe events are structured as follows:\n\n- `[:phoenix_socket_client, :socket]` - Events related to the socket lifecycle.\n- `[:phoenix_socket_client, :channel]` - Events related to channel lifecycle.\n- `[:phoenix_socket_client, :message]` - Events related to messages.\n- `[:phoenix_socket_client, :state]` - Events related to state changes.\n\nThe `action` key in the metadata distinguishes the specific event.\nFor example, a socket connection event is emitted as `[:phoenix_socket_client, :socket]`\nwith metadata `%{action: :connected, ...}`.\n\n### Example Usage\n\n```elixir\n# Attach a telemetry handler\n:telemetry.attach(\n  \"my-handler\",\n  [:phoenix_socket_client, :socket],\n  fn _event_name, _measurements, metadata, _config -\u003e\n    if metadata.action == :connected do\n      IO.puts(\"Socket connected!\")\n    end\n  end,\n  %{}\n)\n\n# Use built-in debug handler\nPhoenix.SocketClient.Telemetry.attach_debug_handler()\n```\n\n### Dependencies\n\nAdd `:telemetry` to your dependencies if using custom handlers:\n\n```elixir\ndef deps do\n  [\n    {:telemetry, \"~\u003e 1.0\"}\n  ]\nend\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgsmlg-dev%2Fphoenix_socket_client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgsmlg-dev%2Fphoenix_socket_client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgsmlg-dev%2Fphoenix_socket_client/lists"}