ExWapp (ExWapp v0.1.2)

Copy Markdown View Source

Stable public client facade for ExWapp.

Public application code should create a %ExWapp.Client{} with new/1 and pass the returned client to the functions in this module. Each mutating operation returns an updated client, so applications normally keep it in their own GenServer, GenStateMachine, or other state owner.

Quick Start

client =
  ExWapp.new(
    session_id: "account_1",
    store: {ExWapp.Store.Ets, path: "/var/lib/my_app/account_1.etf"},
    transport: ExWapp.Client.Transport.Session,
    events: MyApp.WhatsAppEvents
  )

{:ok, client} = ExWapp.connect(client)
{:ok, client, {:code, qr}} = ExWapp.pair(client)

{:ok, client, message_id} =
  ExWapp.send_message(client, to: "393XXXXXXXXX@s.whatsapp.net", text: "hello")

ExWapp.Session and PID-accepting compatibility clauses expose the internal built-in runtime. They may return the same chat and message values, but are not the supported public API and can change as the runtime evolves.

See the README for installation, JSON configuration, storage, events, and the complete public API overview.

Summary

Functions

Returns the unbounded local message stream for a chat.

Archives a chat (server-synced via app state).

Initiates connection to WhatsApp servers.

Controls a session worker using either pid (direct cast) or session_id (PubSub command).

Creates or updates a contact (server-synced via app state).

Deletes a chat (server-synced via app state).

Deletes a contact (server-synced via app state).

Deletes selected local messages after they have been persisted elsewhere.

Returns a compact diagnostics map for a high-level client.

Gracefully disconnects from WhatsApp servers.

Downloads and decrypts image, audio, or document media.

Gets one stored call log entry by call ID.

Gets a specific chat by JID.

Returns local metadata for a specific chat.

Gets a specific contact by JID.

Gets locally retained messages for a chat, newest first.

Returns the session_id for a session process.

Returns send health counters and guard state for a running session.

Returns the last structured error recorded on a high-level client.

Lists the locally stored call log, newest first.

Lists all chats for a session.

Lists all contacts.

Lists only group chats for a session.

Marks a chat as read (server-synced via app state).

Marks all messages in a chat as read.

Builds a lazy stream of incoming messages.

Mutes a chat (server-synced via app state).

Creates a high-level, runtime-neutral client.

Starts pairing for a high-level client.

Pins a chat to the top (server-synced via app state).

Returns current policy status (safety breaker, quota, rate limiter snapshot).

Returns a stream of QR code and pairing events.

Handles an inbound payload through a high-level client transport.

Requests phone-number pairing instead of displaying a QR code.

Requests a fresh direct path for locally stored media by message ID.

Returns the effective runtime configuration for a running session.

Sends audio through a high-level client.

Sends one vCard contact through a high-level client or session process.

Sends a document through a high-level client or a session process.

Sends WhatsApp's experimental calendar event message.

Sends an image through a high-level client.

Sends a GPS location through a high-level client or a session process.

Sends a text message to a contact or group.

Sends a text message and waits for the server's verdict on it.

Sends a read receipt to mark messages as read.

Sends a text message to a WhatsApp contact or group.

Sends a typing indicator to a chat.

Starts a WhatsApp session process.

Returns the current session status.

Returns session statistics for monitoring.

Returns the status of a high-level client.

Builds a deferred stream over the locally stored call log, oldest first.

Builds a lazy stream over locally retained messages.

Subscribe the calling process to a session's events via PubSub.

Subscribe to events from ALL sessions via PubSub.

Fetches and applies the contacts collection from app state (critical_unblock_low).

Unarchives a chat (server-synced via app state).

Unmutes a chat (server-synced via app state).

Unpins a chat (server-synced via app state).

Unsubscribe the calling process from a session's events.

Unsubscribe from all global topics (messages + lifecycle).

Functions

all_messages(target, jid, opts \\ [])

@spec all_messages(ExWapp.Client.t() | GenServer.server(), String.t(), keyword()) ::
  {:ok, Enumerable.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | Enumerable.t()

Returns the unbounded local message stream for a chat.

This is the explicit convenience form of stream_messages/3.

archive_chat(session, jid)

@spec archive_chat(GenServer.server(), String.t()) :: :ok | {:error, term()}

Archives a chat (server-synced via app state).

Falls back to local-only archive if not connected.

connect(client)

@spec connect(ExWapp.Client.t() | GenServer.server()) ::
  {:ok, ExWapp.Client.t()} | {:error, ExWapp.Error.t(), ExWapp.Client.t()} | :ok

Initiates connection to WhatsApp servers.

This starts the Noise handshake and pairing flow. The call returns immediately; use qr_stream/1 to receive pairing events.

Example

{:ok, session} = ExWapp.start_link()
:ok = ExWapp.connect(session)

# Now stream QR codes
ExWapp.qr_stream(session) |> Stream.run()

control_session(session, action)

@spec control_session(
  pid() | binary(),
  :pause_sends | :resume_sends | :disconnect | :stop_session
) :: :ok

Controls a session worker using either pid (direct cast) or session_id (PubSub command).

Supported actions:

  • :pause_sends
  • :resume_sends
  • :disconnect
  • :stop_session

create_contact(client, jid, name)

@spec create_contact(ExWapp.Client.t() | GenServer.server(), String.t(), String.t()) ::
  {:ok, ExWapp.Client.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | :ok
  | {:error, term()}

Creates or updates a contact (server-synced via app state).

delete_chat(session, jid)

@spec delete_chat(GenServer.server(), String.t()) :: :ok | {:error, term()}

Deletes a chat (server-synced via app state).

delete_contact(client, jid)

@spec delete_contact(ExWapp.Client.t() | GenServer.server(), String.t()) ::
  {:ok, ExWapp.Client.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | :ok
  | {:error, term()}

Deletes a contact (server-synced via app state).

delete_messages(client, jid, message_ids)

@spec delete_messages(
  ExWapp.Client.t() | GenServer.server(),
  String.t(),
  [String.t()]
) ::
  {:ok, ExWapp.Client.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | :ok
  | {:error, term()}

Deletes selected local messages after they have been persisted elsewhere.

Delete only IDs that the wrapper has committed successfully. With a client, the updated client is returned; with a direct session/store the result is :ok or a store error.

diagnostics(client)

@spec diagnostics(ExWapp.Client.t()) :: map()

Returns a compact diagnostics map for a high-level client.

disconnect(client)

@spec disconnect(ExWapp.Client.t() | GenServer.server()) ::
  {:ok, ExWapp.Client.t()} | {:error, ExWapp.Error.t(), ExWapp.Client.t()} | :ok

Gracefully disconnects from WhatsApp servers.

Stops automatic reconnection and closes the connection cleanly. Call connect/1 to reconnect.

download_media(target, ref, opts \\ [])

@spec download_media(
  ExWapp.Client.t() | GenServer.server(),
  ExWapp.Media.Ref.t(),
  keyword()
) ::
  {:ok, ExWapp.Client.t(), ExWapp.Media.Download.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | {:ok, ExWapp.Media.Download.t()}
  | {:error, term()}

Downloads and decrypts image, audio, or document media.

get_call(client, call_id)

@spec get_call(ExWapp.Client.t() | GenServer.server(), String.t()) ::
  {:ok, ExWapp.Call.t()}
  | {:error, :not_found}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}

Gets one stored call log entry by call ID.

get_chat(client, jid)

@spec get_chat(ExWapp.Client.t() | GenServer.server(), String.t()) ::
  {:ok, ExWapp.Chat.chat()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | {:error, :not_found}

Gets a specific chat by JID.

Example

{:ok, chat} = ExWapp.get_chat(session, "1234567890@s.whatsapp.net")
IO.inspect(ExWapp.get_messages(session, chat.jid, limit: 20))

get_chat_info(session, jid)

@spec get_chat_info(GenServer.server(), String.t()) ::
  {:ok, ExWapp.Chat.chat()} | {:error, :not_found}

Returns local metadata for a specific chat.

get_contact(client, jid)

@spec get_contact(ExWapp.Client.t() | GenServer.server(), String.t()) ::
  {:ok, ExWapp.Contact.t() | nil}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | ExWapp.Contact.t()
  | nil

Gets a specific contact by JID.

get_messages(target, jid, opts \\ [])

@spec get_messages(ExWapp.Client.t() | GenServer.server(), String.t(), keyword()) ::
  {:ok, [ExWapp.Chat.message()]}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | [ExWapp.Chat.message()]

Gets locally retained messages for a chat, newest first.

With a high-level client this returns {:ok, messages} (or a structured client error); with a direct session it returns the message list.

Options

  • :limit - Maximum messages to return (default: 50), or :all
  • :offset - Number of messages to skip (default: 0)

limit: :all reads every message retained locally for that chat. It does not trigger an on-demand download of older WhatsApp history.

Examples

{:ok, recent} = ExWapp.get_messages(client, jid, limit: 20)
all_local = ExWapp.get_messages(session, jid, limit: :all)

get_session_id(server)

@spec get_session_id(GenServer.server()) :: term() | nil

Returns the session_id for a session process.

Returns nil when no explicit :session_id option was provided at start.

Example

session_id = ExWapp.get_session_id(session)

health_status(server)

@spec health_status(GenServer.server()) :: map()

Returns send health counters and guard state for a running session.

last_error(client)

@spec last_error(ExWapp.Client.t()) :: ExWapp.Error.t() | nil

Returns the last structured error recorded on a high-level client.

list_calls(client_or_session, opts \\ [])

@spec list_calls(
  ExWapp.Client.t() | GenServer.server(),
  keyword()
) ::
  {:ok, [ExWapp.Call.t()]}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | [ExWapp.Call.t()]

Lists the locally stored call log, newest first.

The log is fed by live <call> stanzas (who called, ringing/answered/missed, video or voice) and by the CallLogRecord entries the phone syncs via app-state, which add the authoritative outcome and duration. Timestamps are unix seconds; durations are seconds. See ExWapp.Call.list/2 for all options.

Example

# 20 most recent calls
calls = ExWapp.list_calls(session, limit: 20)

# Missed calls only, since a unix timestamp
missed = ExWapp.list_calls(session, status: :missed, since: 1_754_000_000)

Enum.each(calls, fn call ->
  IO.puts("#{call.chat_jid} #{call.status} #{call.duration || 0}s")
end)

list_chats(client)

@spec list_chats(ExWapp.Client.t() | GenServer.server()) ::
  {:ok, [ExWapp.Chat.chat()]}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | [ExWapp.Chat.chat()]

Lists all chats for a session.

Returns chats sorted by last message timestamp (most recent first).

Example

chats = ExWapp.list_chats(session)
Enum.each(chats, fn chat ->
  IO.puts("#{chat.name || chat.jid} - #{chat.unread_count} unread")
end)

list_contacts(client)

@spec list_contacts(ExWapp.Client.t() | GenServer.server()) ::
  {:ok, [ExWapp.Contact.t()]}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | [ExWapp.Contact.t()]

Lists all contacts.

list_groups(client)

@spec list_groups(ExWapp.Client.t() | GenServer.server()) ::
  {:ok, [ExWapp.Chat.chat()]}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | [ExWapp.Chat.chat()]

Lists only group chats for a session.

Returns chats where JID ends with @g.us, sorted by most recent activity.

mark_chat_as_read(session, jid)

@spec mark_chat_as_read(GenServer.server(), String.t()) :: :ok | {:error, term()}

Marks a chat as read (server-synced via app state).

mark_chat_read(session, jid)

@spec mark_chat_read(GenServer.server(), String.t()) :: :ok

Marks all messages in a chat as read.

Example

ExWapp.mark_chat_read(session, "1234567890@s.whatsapp.net")

message_stream(session)

@spec message_stream(GenServer.server()) :: Enumerable.t()

Builds a lazy stream of incoming messages.

Each element is a tuple of {jid, message}.

Example

ExWapp.message_stream(session)
|> Stream.each(fn {jid, msg} ->
  IO.puts("[#{jid}] #{msg.text}")
end)
|> Stream.run()

mute_chat(session, jid, mute_end_timestamp \\ -1)

@spec mute_chat(GenServer.server(), String.t(), integer()) :: :ok | {:error, term()}

Mutes a chat (server-synced via app state).

mute_end_timestamp is milliseconds epoch, or -1 for forever, or 0/nil to unmute.

new(opts \\ [])

@spec new(keyword()) :: ExWapp.Client.t()

Creates a high-level, runtime-neutral client.

pair(client)

@spec pair(ExWapp.Client.t()) ::
  {:ok, ExWapp.Client.t(), term()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}

Starts pairing for a high-level client.

pin_chat(session, jid)

@spec pin_chat(GenServer.server(), String.t()) :: :ok | {:error, term()}

Pins a chat to the top (server-synced via app state).

policy_status(server)

@spec policy_status(GenServer.server()) :: map()

Returns current policy status (safety breaker, quota, rate limiter snapshot).

qr_stream(server)

@spec qr_stream(GenServer.server()) :: Enumerable.t()

Returns a stream of QR code and pairing events.

Subscribe to this stream after calling connect/1 to receive:

  • {:code, data} - New QR code payload to display (codes expire periodically)
  • {:pairing_code, code} - Phone-number pairing code requested through request_pairing_code/3
  • :success - Successfully paired with phone
  • {:error, reason} - Pairing failed

Example

ExWapp.qr_stream(session)
|> Stream.each(fn
  {:code, data} ->
    # Render QR code (e.g., using qrencode or terminal output)
    qr = ExWapp.Pairing.QR.to_terminal(data)
    IO.puts(qr)

  :success ->
    IO.puts("Paired successfully")

  {:error, :timeout} ->
    IO.puts("QR expired, generating new one...")
end)
|> Stream.run()

receive_message(client, payload, opts \\ [])

@spec receive_message(ExWapp.Client.t(), term(), keyword()) ::
  {:ok, ExWapp.Client.t(), [ExWapp.Event.t()]}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}

Handles an inbound payload through a high-level client transport.

request_pairing_code(session, phone, opts \\ [])

@spec request_pairing_code(GenServer.server(), String.t(), keyword()) ::
  {:ok, String.t()} | {:error, term()}

Requests phone-number pairing instead of displaying a QR code.

Invoke it after connect/1 when the transport is ready for pairing. The returned code is entered in WhatsApp under Link a device with phone number.

retry_media(session, message_id, opts \\ [])

@spec retry_media(GenServer.server(), String.t(), keyword()) ::
  {:ok, ExWapp.Media.Ref.t()} | {:error, term()}

Requests a fresh direct path for locally stored media by message ID.

runtime_config(server)

@spec runtime_config(GenServer.server()) :: map()

Returns the effective runtime configuration for a running session.

send_audio(target, to, source, opts \\ [])

@spec send_audio(
  ExWapp.Client.t() | GenServer.server(),
  String.t(),
  ExWapp.Media.source(),
  keyword()
) ::
  {:ok, ExWapp.Client.t(), String.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | {:ok, String.t()}
  | {:error, term()}

Sends audio through a high-level client.

send_contact(target, to, display_name, vcard, opts \\ [])

@spec send_contact(
  ExWapp.Client.t() | GenServer.server(),
  String.t(),
  String.t(),
  String.t(),
  keyword()
) ::
  {:ok, ExWapp.Client.t(), String.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | {:ok, String.t()}
  | {:error, term()}

Sends one vCard contact through a high-level client or session process.

send_document(target, to, source, opts \\ [])

@spec send_document(
  ExWapp.Client.t() | GenServer.server(),
  String.t(),
  ExWapp.Media.source(),
  keyword()
) ::
  {:ok, ExWapp.Client.t(), String.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | {:ok, String.t()}
  | {:error, term()}

Sends a document through a high-level client or a session process.

send_event(target, to, name, start_time, opts \\ [])

@spec send_event(
  ExWapp.Client.t() | GenServer.server(),
  String.t(),
  String.t(),
  integer() | DateTime.t(),
  keyword()
) ::
  {:ok, ExWapp.Client.t(), String.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | {:ok, String.t()}
  | {:error, term()}

Sends WhatsApp's experimental calendar event message.

send_image(target, to, source, opts \\ [])

@spec send_image(
  ExWapp.Client.t() | GenServer.server(),
  String.t(),
  ExWapp.Media.source(),
  keyword()
) ::
  {:ok, ExWapp.Client.t(), String.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | {:ok, String.t()}
  | {:error, term()}

Sends an image through a high-level client.

send_location(target, to, latitude, longitude, opts \\ [])

@spec send_location(
  ExWapp.Client.t() | GenServer.server(),
  String.t(),
  number(),
  number(),
  keyword()
) ::
  {:ok, ExWapp.Client.t(), String.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | {:ok, String.t()}
  | {:error, term()}

Sends a GPS location through a high-level client or a session process.

send_message(client, opts)

@spec send_message(
  ExWapp.Client.t(),
  keyword()
) ::
  {:ok, ExWapp.Client.t(), String.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}

Sends a text message to a contact or group.

Accepts either a session pid or a session_id string.

When called with a pid, the call is synchronous and returns the message ID. When called with a session_id string, the send command is dispatched via PubSub (fire-and-forget) and returns :ok immediately.

Returns

  • {:ok, message_id} - Message was queued for sending (pid path)
  • :ok - Send command dispatched via PubSub (session_id path)
  • {:error, reason} - Failed to send

Examples

# With session pid (synchronous, returns message_id)
{:ok, id} = ExWapp.send_message(session, "1234567890@s.whatsapp.net", "Hello!")

# With session_id string (fire-and-forget via PubSub)
:ok = ExWapp.send_message("my_session", "1234567890@s.whatsapp.net", "Hello!")

send_message(session, jid, text)

@spec send_message(pid() | binary(), String.t(), String.t()) ::
  {:ok, String.t()} | :ok | {:error, term()}

send_message_await(session, jid, text, timeout \\ 15000)

@spec send_message_await(pid(), String.t(), String.t(), timeout()) ::
  {:ok, String.t()}
  | {:error, {:rejected, ExWapp.Error.Ack.t()}}
  | {:error, :ack_timeout}
  | {:error, term()}

Sends a text message and waits for the server's verdict on it.

send_message/3 returns once the encrypted stanza reaches the socket, which is not the same as WhatsApp accepting it: the server answers a message it accepts with an <ack> and stays silent for one it drops. A caller that treats the write as success records dropped messages as delivered, so the three outcomes are separated here:

  • {:ok, message_id} — the server acknowledged the message
  • {:error, {:rejected, %ExWapp.Error.Ack{}}} — the server refused it, with a classified reason saying whether a resend can succeed
  • {:error, :ack_timeout} — no answer within timeout; genuinely unknown, and specifically not proof of non-delivery

A rejection blaming the Signal session is repaired before it is reported: the peer's sessions are dropped and the message is resent under a fresh one, reusing the same ID, so this returns the verdict on the repaired send. A repair that is rejected in turn is made once more after a pause, because in practice the same payload has gone through seconds later. So timeout has to cover several round trips plus send.session_repair_backoff_ms, not one send — the defaults put that at roughly ten seconds before a session rejection is reported.

Blocks the calling process. Delivery events are consumed from this process's mailbox and any that arrive for other messages are discarded on the way out, so the caller's mailbox is left as it was. A caller that would rather not block should subscribe to "ex_wapp:delivery:<session_id>" directly and correlate on the message ID.

send_read_receipt(session, jid, message_ids)

@spec send_read_receipt(GenServer.server(), String.t(), [String.t()]) ::
  :ok | {:error, term()}

Sends a read receipt to mark messages as read.

Example

ExWapp.send_read_receipt(session, jid, ["MSG_ID_1", "MSG_ID_2"])

send_text(server, jid, text)

@spec send_text(GenServer.server(), binary(), iodata()) ::
  {:ok, String.t()} | {:error, term()}

Sends a text message to a WhatsApp contact or group.

The session must be paired before sending messages. The jid is the recipient's WhatsApp ID (phone number for individuals, group ID for groups).

Examples

# Send to individual (use full JID format)
ExWapp.send_text(session, "1234567890@s.whatsapp.net", "Hello!")

# Send to group
ExWapp.send_text(session, "123456789-987654321@g.us", "Hello group!")

send_typing(session, jid, composing \\ true)

@spec send_typing(GenServer.server(), String.t(), boolean()) :: :ok | {:error, term()}

Sends a typing indicator to a chat.

Example

# Show typing
ExWapp.send_typing(session, jid, true)

# Stop typing
ExWapp.send_typing(session, jid, false)

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Starts a WhatsApp session process.

Options

  • :store - Storage adapter for session state. See ExWapp.Store. Defaults to ExWapp.Store.Ets with default path.

  • :name - Optional name to register the process.

  • :runtime - Per-session runtime config overrides (same shape as config :ex_wapp, :runtime).

  • :client_version / :wa_version - Optional shorthand override for runtime.protocol.client_version. Accepted forms: "2.3000.1034187832", {2, 3000, 1034187832}, or %{primary: 2, secondary: 3000, tertiary: 1034187832}.

Examples

# Anonymous session with default storage
{:ok, session} = ExWapp.start_link()

# Named session with custom storage
{:ok, _} = ExWapp.start_link(
  name: MyApp.WhatsApp,
  store: {ExWapp.Store.Ets, path: "/tmp/wa.etf"}
)

# For testing
{:ok, session} = ExWapp.start_link(store: ExWapp.Store.Memory)

state(server)

Returns the current session status.

Possible values: :idle, :connecting, :handshaking, :syncing, :connected, :disconnected.

Example

iex> ExWapp.state(session)
:connected

stats(server)

@spec stats(GenServer.server()) :: map()

Returns session statistics for monitoring.

Example

iex> ExWapp.stats(session)
%{
  status: :connected,
  connected_at: ~U[2024-01-15 10:30:00Z],
  uptime_seconds: 3600,
  messages_sent: 42,
  messages_received: 128,
  reconnects: 1,
  errors: 0
}

status(client)

@spec status(ExWapp.Client.t()) :: ExWapp.Client.status()

Returns the status of a high-level client.

stream_calls(session, opts \\ [])

@spec stream_calls(
  GenServer.server(),
  keyword()
) :: Enumerable.t()

Builds a deferred stream over the locally stored call log, oldest first.

The bounded retained log is loaded and sorted when enumeration starts, so creating the stream itself performs no store read. Accepts the same options as ExWapp.Call.list/2.

stream_messages(target, jid, opts \\ [])

@spec stream_messages(ExWapp.Client.t() | GenServer.server(), String.t(), keyword()) ::
  {:ok, Enumerable.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | Enumerable.t()

Builds a lazy stream over locally retained messages.

The default order is oldest first, which is suitable for inserting history into a wrapper database. Message payloads are read from the dedicated message store one at a time; the complete history is not stored in or loaded through the chat struct.

Options

  • :order - :oldest_first (default) or :newest_first
  • :offset - Number of records to skip (default: 0)
  • :limit - Optional maximum; defaults to :all

This stream represents local history and does not request older messages from WhatsApp.

subscribe(session)

@spec subscribe(pid() | term()) :: :ok | {:error, term()}

Subscribe the calling process to a session's events via PubSub.

Subscribes to both incoming messages and session lifecycle events in a single call. Accepts either a session pid or a session_id string.

Messages received

  • {:ex_wapp_message, jid, message} - incoming WhatsApp message
  • {:ex_wapp_call, call} - call state change (%ExWapp.Call{}: ringing, answered, missed, ...)
  • {:ex_wapp_session, :connected} - session connected
  • {:ex_wapp_session, :error, reason} - session-level error event
  • {:ex_wapp_session, :disconnected, reason} - session disconnected
  • {:ex_wapp_session, :terminated, reason} - session process stopped
  • {:ex_wapp_health, type, metadata} - send health/error/guard events

Example

ExWapp.subscribe(session)
# or
ExWapp.subscribe("my_session")

def handle_info({:ex_wapp_message, jid, message}, state) do
  IO.puts("Message from #{jid}")
  {:noreply, state}
end

def handle_info({:ex_wapp_session, :connected}, state) do
  {:noreply, assign(state, :status, :connected)}
end

def handle_info({:ex_wapp_session, :disconnected, _reason}, state) do
  {:noreply, assign(state, :status, :disconnected)}
end

subscribe_all()

@spec subscribe_all() :: :ok | {:error, term()}

Subscribe to events from ALL sessions via PubSub.

Subscribes to both global messages and global lifecycle topics.

Messages received

  • {:ex_wapp_message, session_id, jid, message} - message from any session
  • {session_id, {:ex_wapp_call, call}} - call state change from any session
  • {session_id, {:ex_wapp_session, event, ...}} - lifecycle from any session
  • {session_id, {:ex_wapp_health, type, metadata}} - health events from any session

sync_contacts(client)

@spec sync_contacts(ExWapp.Client.t() | GenServer.server()) ::
  {:ok, ExWapp.Client.t()}
  | {:error, ExWapp.Error.t(), ExWapp.Client.t()}
  | :ok
  | {:error, term()}

Fetches and applies the contacts collection from app state (critical_unblock_low).

unarchive_chat(session, jid)

@spec unarchive_chat(GenServer.server(), String.t()) :: :ok | {:error, term()}

Unarchives a chat (server-synced via app state).

unmute_chat(session, jid)

@spec unmute_chat(GenServer.server(), String.t()) :: :ok | {:error, term()}

Unmutes a chat (server-synced via app state).

unpin_chat(session, jid)

@spec unpin_chat(GenServer.server(), String.t()) :: :ok | {:error, term()}

Unpins a chat (server-synced via app state).

unsubscribe(session)

@spec unsubscribe(pid() | term()) :: :ok

Unsubscribe the calling process from a session's events.

Unsubscribes from both messages and lifecycle topics. Accepts either a session pid or a session_id string.

unsubscribe_all()

@spec unsubscribe_all() :: :ok

Unsubscribe from all global topics (messages + lifecycle).