# `ExWapp.Proto.HistorySync`
[🔗](https://github.com/elchemista/ex_wapp/blob/v0.1.5/lib/ex_wapp/proto/history_sync.ex#L1)

Extracts WhatsApp history sync payloads embedded in protocol messages.

Mirrors the flow used in reference clients:
- `Message.protocolMessage.historySyncNotification`
- `initialHistBootstrapInlinePayload` (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.

# `blob_ref`

```elixir
@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.

# `conversation`

```elixir
@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()]
}
```

# `conversation_message`

```elixir
@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
}
```

# `t`

```elixir
@type t() :: %{
  sync_type: atom() | non_neg_integer() | nil,
  conversations: [conversation()],
  lid_mappings: [{String.t(), String.t()}],
  call_log_records: [ExWapp.Call.t()]
}
```

# `extract_from_message`

```elixir
@spec extract_from_message(binary()) ::
  {:ok, t()}
  | {:pending_blob, blob_ref()}
  | :not_history_sync
  | {:error, term()}
```

# `parse_history_payload`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
