Extracts WhatsApp history sync payloads embedded in protocol messages.
Mirrors the flow used in reference clients:
Message.protocolMessage.historySyncNotificationinitialHistBootstrapInlinePayload(zlib-compressed)HistorySync.conversations
Decoding uses the generated WAWebProtobufsHistorySync modules; this module
only flattens the structs into the internal map contract.
Inline chunks versus blob chunks
WhatsApp delivers the bootstrap chunk inline (initialHistBootstrapInlinePayload),
and the larger RECENT/FULL chunks as encrypted media the client is expected to
download. extract_from_message/1 returns inline chunks immediately and
returns {:pending_blob, blob_ref()} for media-delivered chunks. The session
downloads those chunks in supervised background tasks and feeds the decrypted
compressed bytes to parse_history_payload/2, then uses the same projection
as inline history. This does not request additional history from the phone.
Summary
Types
Everything needed to download and decrypt a history-sync chunk delivered as media instead of inline. Kept verbatim from the notification so the background downloader needs no re-parse.
Functions
Flattens a HistorySync payload into the internal contract.
Types
@type blob_ref() :: %{ sync_type: atom() | non_neg_integer() | nil, chunk_order: non_neg_integer() | nil, progress: non_neg_integer() | nil, oldest_msg_timestamp: non_neg_integer() | nil, media_key: binary() | nil, direct_path: String.t() | nil, enc_handle: String.t() | nil, file_sha256: binary() | nil, file_enc_sha256: binary() | nil, file_length: non_neg_integer() | nil }
Everything needed to download and decrypt a history-sync chunk delivered as media instead of inline. Kept verbatim from the notification so the background downloader needs no re-parse.
@type conversation() :: %{ jid: String.t(), name: String.t() | nil, unread_count: non_neg_integer() | nil, last_message_timestamp: non_neg_integer() | nil, pinned: boolean() | nil, archived: boolean() | nil, muted_until: non_neg_integer() | nil, is_group: boolean(), pn_jid: String.t() | nil, lid_jid: String.t() | nil, tc_token: binary() | nil, tc_token_timestamp: non_neg_integer() | nil, tc_token_sender_timestamp: non_neg_integer() | nil, messages: [conversation_message()] }
@type conversation_message() :: %{ id: String.t() | nil, from_me: boolean(), timestamp: non_neg_integer() | nil, text: String.t() | nil, participant: String.t() | nil, remote_jid: String.t() | nil, order_id: non_neg_integer() | nil }
@type t() :: %{ sync_type: atom() | non_neg_integer() | nil, conversations: [conversation()], lid_mappings: [{String.t(), String.t()}], call_log_records: [ExWapp.Call.t()] }
Functions
@spec parse_history_payload( {:ok, binary()} | {:compressed, binary()} | binary(), term() ) :: {:ok, t()} | {:error, term()}
Flattens a HistorySync payload into the internal contract.
The inline path passes already-inflated bytes. Downloaded chunks use
{:compressed, bytes} to validate zlib compression before decoding protobuf.