{"id":23658883,"url":"https://github.com/mustafaturan/sse","last_synced_at":"2025-04-03T03:10:31.364Z","repository":{"id":48306450,"uuid":"122154408","full_name":"mustafaturan/sse","owner":"mustafaturan","description":"Server Sent Events for Elixir/Plug","archived":false,"fork":false,"pushed_at":"2021-08-02T13:15:16.000Z","size":188,"stargazers_count":88,"open_issues_count":2,"forks_count":4,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-05-01T20:47:10.763Z","etag":null,"topics":["elixir","elixir-plug","server-sent-events","sse"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mustafaturan.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-02-20T04:16:46.000Z","updated_at":"2024-03-03T17:58:22.000Z","dependencies_parsed_at":"2022-09-26T18:50:38.931Z","dependency_job_id":null,"html_url":"https://github.com/mustafaturan/sse","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafaturan%2Fsse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafaturan%2Fsse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafaturan%2Fsse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafaturan%2Fsse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mustafaturan","download_url":"https://codeload.github.com/mustafaturan/sse/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246927835,"owners_count":20856198,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["elixir","elixir-plug","server-sent-events","sse"],"created_at":"2024-12-29T01:03:03.887Z","updated_at":"2025-04-03T03:10:31.342Z","avatar_url":"https://github.com/mustafaturan.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SSE\n\n[![Build Status](https://travis-ci.org/mustafaturan/sse.svg?branch=master)](https://travis-ci.org/mustafaturan/sse)\n\nServer Sent Events for Elixir/Plug.\n\nServer-Sent Events (SSE) is a lightweight and standardized protocol for pushing notifications from a HTTP server to a client. In contrast to WebSocket, which offers bi-directional communication, SSE only allows for one-way communication from the server to the client. If that’s all you need, SSE has the advantages to be much simpler, to rely on HTTP 1.1 only and to offer retry semantics on broken connections by the browser.\n\n## Table of Contents\n\n[Installation](#installation)\n\n[Data Structures](#data-structures)\n\n- [Chunk](#chunk)\n\n- [Event](#event)\n\n[Usage](#usage)\n\n- [Phoenix Framework](#phoenix-framework)\n\n- [Standalone](#standalone-with-plug-withwithout-any-framework)\n\n[Docs](#docs)\n\n[Contributing](#contributing)\n\n[License](#license)\n\n## Installation\n\nThe package can be installed by adding `sse` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:sse, \"~\u003e 0.4\"},\n    {:event_bus, \"\u003e= 1.6.0\"}\n  ]\nend\n```\n\n**Note:** It is highly recommended to use latest version of `event_bus` library. Please make sure that `event_bus` app starts earlier than `sse` library.\n\n## Data Structures\n\nTo send chunks of events to client you need to create a `SSE.Chunk` data structure and Event data structure to deliver events.\n\n### Chunk\n\nChunk has following attributes and only the *`data`* attribute is *required*, the rest of the attributes are optional:\n\n`comment` - The comment line can be used to prevent connections from timing out; a server can send a comment periodically to keep the connection alive. Note: SSE package keeps connection alive for you, you don't have to send comment.\n\n`data` - The data field for the message. When the EventSource receives multiple consecutive lines that begin with data:, it will concatenate them, inserting a newline character between each one. Trailing newlines are removed.\n\n`event` - A string identifying the type of event described. If this is specified, an event will be dispatched on the browser to the listener for the specified event name; the web site source code should use addEventListener() to listen for named events. The onmessage handler is called if no event name is specified for a message.\n\n`id` - The event ID to set the EventSource object's last event ID value.\n\n`retry` - The reconnection time to use when attempting to send the event. This must be an integer, specifying the reconnection time in milliseconds. If a non-integer value is specified the field is ignored.\n\nSample data preperation\n\n```elixir\nchunk = %SSE.Chunk{data: [\"some data\", \"another data\"]}\n```\n\n### Event\n\nTo deliver chunks, you need to notify an `%EventBus.Model.Event{}` struct to the desired topic.\n\nAn `Event` struct may have at least 3 values:\n\n`id` - Unique event identifier (`integer | String.t`)\n\n`data` - Chunk data (`SSE.Chunk.t`)\n\n`topic` - Name of the topic to deliver event (`atom`)\n\nSample data preparation\n\n```elixir\nchunk = %SSE.Chunk{data: \"some data\"}\nevent = EventBus.Model.Event{id: UUID.uuid4(), data: chunk, topic: :a_topic_name}\n```\n\n## Usage\n\nSSE designed to work with any Plug app. So, it can be used with/without Phoenix Framework.\n\n### Phoenix Framework\n\nIn your `config.exs`, register events before the app start:\n\n```elixir\nconfig :sse,\n  keep_alive: {:system, \"SSE_KEEP_ALIVE_IN_MS\", 1000} # Keep alive in milliseconds\n\nconfig :event_bus,\n  topics: [:usd_eur_pair_updated, ...] # let's say we have a usd_eur_pair_updated event\n\n```\n\nIn your controller:\n\n```elixir\ndefmodule ExchangeRateController do\n  @topic :usd_eur_pair_updated\n\n  alias SSE.Chunk\n\n  ...\n\n  # Sample action to display USD to EUR exchange rates\n  def show(conn, _params) do\n    rates = %{rates: Ticker.fetch()}\n    chunk = %Chunk(data: Poison.encode!(rates))\n\n    SSE.stream(conn, {[@topic], chunk})\n  end\n\n  ...\n\nend\n```\n\nLet's assume you have a ticker to update exchange rates:\n\n```elixir\ndefmodule Ticker do\n  alias EventBus.Model.Event\n\n  @topic :usd_eur_pair_updated\n\n  ...\n\n  def start_link do\n    GenServer.start_link(__MODULE__, [], name: __MODULE__)\n  end\n\n  def fetch do\n    GenServer.call(__MODULE__, {:fetch})\n  end\n\n  def init(_) do\n    # Let's update the rates info every second\n    update_exchange_rates_later()\n    {:ok, fetch_rates()}\n  end\n\n  def handle_info(:update_exchange_rates, state) do\n    new_rates = fetch_rates()\n    unless state == new_rates do\n      rates = %{rates: new_rates}\n      chunk = %Chunk{data: Poison.encode!(rates)}\n      event = %Event{id: UUID.uuid4(), data: chunk, topic: @topic}\n      EventBus.notify(event)\n    end\n\n    update_exchange_rates_later()\n    {:noreply, new_rates}\n  end\n\n  def handle_call({:fetch}, _from, state) do\n    {:reply, state, state}\n  end\n\n  defp fetch_rates do\n    # Get rates from somewhere...\n  end\n\n  defp update_exchange_rates_later do\n    Process.send_after(self(), :update_exchange_rates, 1000)\n  end\n\n  ...\nend\n```\n\n### Standalone (with Plug, with/without any framework)\n\nAll you need to change is your controller as below, the rest is the same as the Phoenix framework sample:\n\n```elixir\ndefmodule ExchangeRateController do\n\n  @topic :usd_eur_pair_updated\n\n  alias SSE.Chunk\n\n  ...\n\n  get \"/exchange_rates/usd_eur\" do\n    rates = %{rates: Ticker.fetch()}\n    chunk = %Chunk{data: Poison.encode!(rates)}\n\n    conn\n    |\u003e Conn.put_resp_header(\"Access-Control-Allow-Origin\", \"*\")\n    |\u003e SSE.stream({[@topic], chunk})\n  end\n\n  ...\n\nend\n```\n\n## Cowboy Dependency Notes\n\nIf you are using cowboy \u003e= `2.5.0` then you need to pass `:idle_timeout` option to cowboy server configuration to not to face timeouts after 60 seconds.\n\nChanges on cowboy: https://github.com/ninenines/cowboy/commit/a45813c60f0f983a24ea29d491b37f0590fdd087#diff-eb7ad6798a2ba75c9e305f8f55b87402R160\n\nDetails: https://ninenines.eu/docs/en/cowboy/2.5/manual/cowboy_http/\n\n**Sample config Cowboy server config:**\n\n```elixir\ndefmodule Web.Router do\n  use Plug.Router\n\n  plug(:match)\n  plug(:dispatch)\n\n  ...\nend\n\ndefmodule Web.RouterSupervisor do\n  @moduledoc \"\"\"\n  A server supervisor using Cowboy web server\n  \"\"\"\n\n  use Supervisor\n\n  alias Plug.Adapters.Cowboy\n  alias Web.Router # Your router\n\n  @doc false\n  def init(opts) do\n    opts\n  end\n\n  @doc false\n  def start_link do\n    {:ok, _} = Cowboy.http(\n        Router,\n        [],\n        port: 4000,\n        compress: true,\n        protocol_options: [idle_timeout: :infinity]\n      )\n  end\nend\n```\n\n## Docs\n\nThe module docs can be found at [https://hexdocs.pm/sse](https://hexdocs.pm/sse).\n\nReference: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events\n\n## Contributing\n\n### Issues, Bugs, Documentation, Enhancements\n\nCreate an issue if there is a bug.\n\nFork the project.\n\nMake your improvements and write your tests(make sure you covered all the cases).\n\nMake a pull request.\n\n## License\n\nMIT\n\nCopyright (c) 2018 Mustafa Turan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmustafaturan%2Fsse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmustafaturan%2Fsse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmustafaturan%2Fsse/lists"}