OTP ERLANG Wiregrid Plainwire · Manual 1.0

Application API

Chat façade

Wiregrid.Chat is the opinionated path for channel chat, direct messages, typing, reactions, edits, deletes, and read markers. It calls the same runtime as Wiregrid.publish/4. There is no second engine and no second set of sessions.

Defining the module

defmodule MyApp.Chat do
  use Wiregrid.Chat,
    instance: :chat,
    options: [profile: :small, authorizer: MyApp.Auth],
    chat: [max_message_bytes: 8_192]
end

instance is required. options is the keyword list passed to Wiregrid.Config and defaults to [], which selects the :balanced profile. chat is validated by Wiregrid.Chat.Config at compile time; unknown keys raise ArgumentError.

The generated module defines child_spec/1, so it drops into a supervision tree. It also defines instance/0, wiregrid_options/0, chat_config/0, and gateway_child_spec/1, which starts Wiregrid.Foreign.Gateway bound to the same instance. child_spec/1 is overridable.

mix wiregrid.gen.chat MyApp.Chat --instance chat --profile small writes a module of this shape.

Connect, join, say, history

{:ok, session_id} = MyApp.Chat.connect("alice")
:ok = MyApp.Chat.join(session_id, "general")
{:ok, result} = MyApp.Chat.say(session_id, "general", "hello")
{:ok, rows, next} = MyApp.Chat.history("general", nil, 100)

connect/3 is connect(user_id, pid \\ self(), opts \\ []) and returns {:ok, session_id}. join/3 subscribes to {:channel, channel}. An optional context map is stored with the subscription.

say/4 builds a %{type: :message, body: body} event and publishes it with class: :durable, persist: true, and session_id set to the speaker. Persistence uses the channel topic as the storage stream, so history/3 pages the same rows. The default page size is 100. history(channel, cursor \\ nil, limit \\ 100) returns {:ok, rows, next_cursor}.

Say options are the message fields :attachments, :mentions, :metadata, :nonce, :reply_to, plus the publish fields :event_id, :meta, :cluster, :exclude_sessions, and :exclude_users. Duplicate keys are rejected as :invalid_chat_options.

Whisper and the user stream

whisper(session_id, user_id, body, opts \\ []) sends the same message event with Wiregrid.send_user/4. The stream is {:user, user_id}. Read it back with inbox_history(user_id, cursor \\ nil, limit \\ 100). Whisper options are :event_id, :meta, and :cluster, plus the message fields. The call forces class: :durable and persist: true.

Edits, deletes, reactions, typing, reads

CallEventClass
edit(session, channel, message_id, body)%{type: :message_edit, message_id:, body:}durable, persisted
delete(session, channel, message_id, reason \\ nil)%{type: :message_delete, message_id:, reason:}durable, persisted
react/4 and unreact/4:reaction_add or :reaction_removedurable, persisted
mark_read(session, channel, message_id)%{type: :read, message_id:}ephemeral, not persisted, sender excluded
typing(session, channel, active? \\ true)%{type: :typing, active: boolean}ephemeral, not persisted, sender excluded

Bodies are binaries. Wiregrid.Chat.Event trims them and rejects a body larger than max_message_bytes with :message_too_large. An empty body with an empty attachment list is :empty_message. An empty body with attachments is allowed when allow_empty_messages_with_attachments is true, which is the default. Edits still require a non-empty body. Reactions reject an empty emoji with :empty_reaction. Message ids are binaries up to 256 bytes, or an integer or atom.

Constructors drop nil, [], and %{} fields so a plain text message stays a small map. The finished event must fit in max_chat_event_bytes.

set_presence(session_id, status, metadata \\ %{}) forwards to Wiregrid.set_presence/4. ack/1 accepts a map with session_id and delivery_id and acknowledges that one delivery. Any other term is {:error, :invalid_delivery_envelope}.

Chat limits

These defaults live in Wiregrid.Chat.Config. They are separate from the instance profile.

KeyDefaultCeiling
max_message_bytes65,5361,048,576, and not above max_chat_event_bytes
max_attachment_count32256
max_mention_count10010,000
max_reaction_bytes64positive integer
max_client_nonce_bytes128positive integer
max_chat_event_bytes262,1441,048,576
message_rate_limit{30, 10_000}limit 1..100,000, window 100..3,600,000 ms, or nil
activity_rate_limit{60, 10_000}same shape, or nil
allow_empty_messages_with_attachmentstrueboolean

Say, whisper, edit, and delete spend the message limiter. React and typing spend the activity limiter. Both use a token bucket whose burst equals the limit, keyed by the session id under :chat_message or :chat_activity. A nil rate skips the check. Metadata maps are capped at 128 keys. Attachment and mention lists that exceed their counts return {:error, {:invalid_or_too_many, field}}.

What arrives in the mailbox

Joiners receive {:"$wiregrid", envelope}. With the default delivery_format: :term the map contains :event and the chat map above. Durable says keep a delivery reservation until MyApp.Chat.ack(envelope) or Wiregrid.ack(instance, session_id, delivery_id). Typing and read markers are ephemeral: past the soft queue they are dropped, and they do not occupy storage.

Wiregrid 1.0 · Plainwire · github.com/Plainwire-development/Wiregrid