server_sent_event v1.0.0 ServerSentEvent

Push updates to Web clients over HTTP or using dedicated server-push protocols.

Messages are sent in the following form, with the text/event-stream MIME type:

data: This is the first message.

data: This is the second message, it
data: has two lines.

event: custom
data: This message has event type 'custom'.

A living standard is available from WHATWG.

The contents of a server-sent-event are:

typeThe type of an event
linesThe data contents of the event split by line
idValue to send in last-event-id header when reconnecting
retryTime to wait before retrying connection in milliseconds
commentsAny lines from original block that were marked as comments

Link to this section Summary

Functions

Does the event have any data lines.

This event stream format's MIME type is text/event-stream.

Parse the next event from text stream, if present.

Parse all events from text stream.

Format an event to be sent as part of a stream

Link to this section Types

Link to this type

t()
t() :: %ServerSentEvent{
  comments: [String.t()],
  id: nil | String.t(),
  lines: [String.t()],
  retry: nil | integer(),
  type: nil | String.t()
}

Link to this section Functions

Link to this function

empty?(event)
empty?(event :: t()) :: boolean()

Does the event have any data lines.

An event without any data lines will not trigger any browser events.

Link to this function

mime_type()
mime_type() :: String.t()

This event stream format's MIME type is text/event-stream.

Link to this function

new(data, opts \\ [])
new(String.t(), list()) :: t()

Create a ServerSentEvent struct.

Examples

iex> SSE.new("my data")
...> |> Map.get(:lines)
["my data"]

iex> SSE.new("some\r\nlines")
...> |> Map.get(:lines)
["some", "lines"]

iex> SSE.new("some\nlines")
...> |> Map.get(:lines)
["some", "lines"]

iex> SSE.new("my data", id: "45")
...> |> Map.get(:id)
"45"

iex> SSE.new("my data", retry: 45)
...> |> Map.get(:retry)
45

iex> SSE.new("my data", type: "update")
...> |> Map.get(:type)
"update"
Link to this function

parse(stream)
parse(String.t()) ::
  {:ok, {event :: t() | nil, rest :: String.t()}} | {:error, term()}

Parse the next event from text stream, if present.

Examples

In these examples this module has been aliased to SSE.

iex> SSE.parse("data: This is the first message\n\n")
{:ok, {%SSE{lines: ["This is the first message"]}, ""}}

iex> SSE.parse("data:First whitespace character is optional\n\n")
{:ok, {%SSE{lines: ["First whitespace character is optional"]}, ""}}

iex> SSE.parse("data: This message\ndata: has two lines.\n\n")
{:ok, {%SSE{lines: ["This message", "has two lines."]}, ""}}

iex> SSE.parse("data: This is the first message\n\nrest")
{:ok, {%SSE{lines: ["This is the first message"]}, "rest"}}

iex> SSE.parse("data: This message is not complete")
{:ok, {nil, "data: This message is not complete"}}

iex> SSE.parse("This line is invalid\nit doesn't contain a colon\n")
{:error, {:malformed_line, "This line is invalid"}}

iex> SSE.parse("event: custom\ndata: This message is type custom\n\n")
{:ok, {%SSE{type: "custom", lines: ["This message is type custom"]}, ""}}

iex> SSE.parse("id: 100\ndata: This message has an id\n\n")
{:ok, {%SSE{id: "100", lines: ["This message has an id"]}, ""}}

iex> SSE.parse("retry: 5000\ndata: This message retries after 5s.\n\n")
{:ok, {%SSE{retry: 5000, lines: ["This message retries after 5s."]}, ""}}

iex> SSE.parse("retry: five thousand\ndata: retry value is not a valid integer\n\n")
{:error, {:invalid_retry_value, "five thousand"}}

iex> SSE.parse(": This is a comment\n\n")
{:ok, {%SSE{comments: ["This is a comment"]}, ""}}

iex> SSE.parse("data: data can have more :'s in it'\n\n")
{:ok, {%SSE{lines: ["data can have more :'s in it'"]}, ""}}

iex> SSE.parse("DATA: field names are case-sensitive\n\n")
{:error, {:invalid_field_name, "DATA"}}

iex> SSE.parse("unknown: what is this field?\n\n")
{:error, {:invalid_field_name, "unknown"}}

# It is possible for an event stream using `CRLF` to be split mid line delimiter.
# In this case the parser needs to clear the leading newline character.
iex> SSE.parse("data: This is the first message\r\n\r")
{:ok, {%SSE{lines: ["This is the first message"]}, ""}}

iex> SSE.parse("\ndata: This is the second message\r\n\r\n")
{:ok, {%SSE{lines: ["This is the second message"]}, ""}}
Link to this function

parse_all(stream)
parse_all(String.t()) ::
  {:ok, {[event :: t()], rest :: String.t()}} | {:error, term()}

Parse all events from text stream.

Examples

In these examples this module has been aliased to SSE.

iex> SSE.parse_all("data: First message\n\ndata: Second\ndata: message\n\nrest")
{:ok,
  {
    [
      %SSE{lines: ["First message"]},
      %SSE{lines: ["Second", "message"]}
    ],
    "rest"
  }
}

iex> SSE.parse_all("data: This is the first message\n\n")
{:ok, {[%SSE{lines: ["This is the first message"]}], ""}}

iex> SSE.parse_all("data: This is the first message\n\nrest")
{:ok, {[%SSE{lines: ["This is the first message"]}], "rest"}}

iex> SSE.parse_all("data: This message is not complete")
{:ok, {[], "data: This message is not complete"}}

iex> SSE.parse_all("This line is invalid\nit doesn't contain a colon\n")
{:error, {:malformed_line, "This line is invalid"}}

iex> SSE.parse_all("data: This is the first message\n\nThis line is invalid\n")
{:error, {:malformed_line, "This line is invalid"}}

iex> SSE.parse_all("data: This is the first message\n\nThis line is yet to terminate")
{:ok, {[%SSE{lines: ["This is the first message"]}], "This line is yet to terminate"}}
Link to this function

serialize(event)
serialize(event :: t()) :: String.t()

Format an event to be sent as part of a stream

serialize accepts the same arguments as new to create and serialize in one step.

NOTE: Each data/comment line must be without new line charachters.

Examples

In these examples this module has been aliased to SSE.

iex> SSE.serialize("my data", type: "update")
"event: update\ndata: my data\n\n"

iex> %SSE{type: "greeting", lines: ["Hi,", "there"], comments: ["comment"]}
...> |> SSE.serialize()
"event: greeting\n: comment\ndata: Hi,\ndata: there\n\n"

iex> %SSE{lines: ["message with id"], id: "some-id"}
...> |> SSE.serialize()
"data: message with id\nid: some-id\n\n"

iex> %SSE{lines: ["message setting retry to 10s"], retry: 10_000}
...> |> SSE.serialize()
"data: message setting retry to 10s\nretry: 10000\n\n"
Link to this function

serialize(data, opts \\ [])
serialize(String.t(), list()) :: String.t()