Call log storage and queries.
One %ExWapp.Call{} describes one call: who started it, where it belongs,
how it ended, and how long it lasted. Records are built from three sources
and merged by call ID, so a call first seen live as <call> stanzas can
later be enriched by the authoritative CallLogRecord the phone syncs:
:live— inbound<call>signalling stanzas (offer/accept/terminate/...):app_state—call_logapp-state mutations carryingCallLogRecord:history_sync—HistorySync.callLogRecordsentries
Storage
Records are persisted via the session's store adapter under the :call_log
key, bounded to max_records entries (oldest dropped first) so an active
account cannot grow the store without limit. The bound is configurable via
config.calls.max_records.
Usage
# Most recent calls, newest first (limit defaults to 50)
calls = ExWapp.Call.list(session, limit: 20)
# Only missed incoming calls since a unix timestamp
missed = ExWapp.Call.list(session, status: :missed, since: 1_754_000_000)
# Deferred chronological traversal over the bounded retained log
ExWapp.Call.stream(session) |> Enum.each(&IO.inspect/1)
# Timestamps are unix seconds
DateTime.from_unix!(call.start_time)
Summary
Functions
Deletes every stored call record.
Counts stored call records.
Default bound on stored call records, used when no configuration overrides
config.calls.max_records.
Deletes stored 1:1 call records for one peer.
Converts a CallLogRecord protobuf into a call struct.
Gets one stored call by call ID.
Lists stored calls, newest first by default.
Applies one CallLogRecord coming from app-state or history sync.
Builds a deferred stream over stored calls.
Inserts or merges one call record by ID and prunes the log to the bound.
Types
@type status() ::
:ringing
| :ongoing
| :connected
| :missed
| :rejected
| :cancelled
| :accepted_elsewhere
| :failed
| :unavailable
| :upcoming
| :abandoned
| :invalid
| :silenced
| :unknown
Lifecycle status of a call.
:ringing and :ongoing are transient live states; everything else is a
final outcome. :missed means an incoming call ended unanswered,
:cancelled an outgoing call ended before the peer accepted, and
:accepted_elsewhere that another device of this account took the call.
@type t() :: %ExWapp.Call{ accepted_at: non_neg_integer() | nil, call_type: :regular | :scheduled_call | :voice_chat | nil, chat_jid: String.t() | nil, creator_jid: String.t() | nil, duration: non_neg_integer() | nil, end_reason: String.t() | nil, ended_at: non_neg_integer() | nil, group_jid: String.t() | nil, id: String.t() | nil, is_group: boolean(), is_incoming: boolean() | nil, is_video: boolean() | nil, participants: [participant()], source: :live | :app_state | :history_sync | nil, start_time: non_neg_integer() | nil, status: status() | nil }
Functions
Deletes every stored call record.
@spec count(target()) :: non_neg_integer()
Counts stored call records.
@spec default_max_records() :: pos_integer()
Default bound on stored call records, used when no configuration overrides
config.calls.max_records.
@spec delete_by_peer(target(), String.t() | ExWapp.JID.t(), boolean() | nil) :: :ok | {:error, term()}
Deletes stored 1:1 call records for one peer.
Mirrors the app-state DeleteIndividualCallLogAction: is_incoming limits
the deletion to one direction, nil removes both directions.
@spec from_call_log_record( WAWebProtobufSyncAction.CallLogRecord.t() | nil, keyword() ) :: t() | nil
Converts a CallLogRecord protobuf into a call struct.
Returns nil when the record has no call ID. :source defaults to
:app_state; the history-sync ingester passes source: :history_sync.
Gets one stored call by call ID.
Lists stored calls, newest first by default.
Options
:limit— max records returned,:allfor everything retained (default50):offset— records skipped before taking:limit(default0):order—:newest_first(default) or:oldest_first:status— astatus/0atom or list of them:jid— only calls belonging to this chat/group JID:since/:until— unix-second bounds onstart_time
@spec merge_from_sync( target(), WAWebProtobufSyncAction.CallLogRecord.t() | nil, keyword() ) :: :ok | :ignored
Applies one CallLogRecord coming from app-state or history sync.
Returns :ok when the record was stored, :ignored when it carries no
usable call ID.
@spec stream( target(), keyword() ) :: Enumerable.t()
Builds a deferred stream over stored calls.
Defaults to oldest first with no limit so a wrapper can export the log in
chronological order. The retained call-log snapshot is loaded and sorted on
first enumeration, not when the stream is created; it is bounded by
config.calls.max_records. Accepts the same options as list/2.
Inserts or merges one call record by ID and prunes the log to the bound.
Field-level merge: incoming non-nil fields from an equally or more
authoritative source win. App-state and history-sync records are
authoritative over live signalling; a later live stanza may fill a missing
field but cannot replace a synchronized value or outcome. Transient status
progression is monotonic (:ringing then :ongoing) and never overwrites a
final outcome. When both accepted_at and ended_at are known and
duration is not, the duration is derived from their difference.
Options
:max_records— retention bound (default500)