Amarula.Config (amarula v0.4.3)

View Source

Connection config + the single source of truth for protocol/connection defaults.

There are two kinds of configuration:

1. Per-connection config — passed to Amarula.new/1

A map; you supply only what differs (at minimum :profile), merge/1 fills the rest from the defaults below.

KeyDefaultMeaning
:profile— (required)names + scopes this account's stored state
:storage{Amarula.Storage.File, root: "./amarula_data"} (root uses AMARULA_DATA_DIR when set)storage backend {module, opts}
:retry_cacheETS (see Amarula.RetryCache)sent-message cache backend + opts, e.g. {Amarula.RetryCache.ETS, max_entries: 1000} to raise the cap (default 200, evicts oldest). See Amarula.RetryCache
:registryAmarula.ProfileRegistry (local){module, name} or bare name for the profile→connection registry; swap for Horde.Registry to enforce one-conn-per-profile cluster-wide (default: per node). See Amarula.ProfileRegistry
:authloaded from storageexplicit creds (advanced; normally Amarula loads/persists these itself)
:version[2, 3000, 1042537629]WhatsApp Web protocol version — MUST track a version WhatsApp still accepts or the handshake is rejected. The live value drifts; override the pinned default without recompiling via the AMARULA_WA_VERSION env var (see wa_version/0), or bump the pinned literal with mix run scripts/update_wa_version.exs. (Distinct from Baileys source parity — see Amarula.Baileys / docs/PARITY.md.)
:browser["Mac OS", "Chrome", "14.4.1"]browser triple [os, client, version] shown as the linked device. If the client (2nd element) contains "Android" (case-insensitive, e.g. ["MyApp", "Android", ""]), the connection registers as an Android client instead of WhatsApp Web — see the impact note below.
:max_retries5reconnect attempts
:retry_delay1000base reconnect backoff (ms)
:connect_timeout_ms30_000WebSocket connect timeout
:keep_alive_interval_ms30_000WA-level keep-alive ping interval
:sender_idle_ms1_000how long a per-recipient ConversationSender stays warm after its last send before stopping. Larger = fewer respawns/session re-reads under bursty traffic (useful with a disk-backed store); smaller = sheds processes faster after a fan-out
:sync_full_historytruerequest full history on link
:mark_online_on_connecttruesend presence-available on connect. false keeps this session unavailable — it appears offline to others and the primary phone keeps receiving push notifications (live messages are then queued offline rather than pushed to this session).
:fire_init_queriestruerun the post-login init IQ queries
:country_code"US"
:headers / :origin / :agentsee defaultsHTTP/WS handshake
Amarula.new(%{profile: :me, sync_full_history: false}) |> Amarula.connect()

Android browser mode (impact)

Setting an Android :browser (e.g. ["MyApp", "Android", ""]) registers the linked device as an Android client rather than WhatsApp Web. This changes the registration/login payload in three ways (mirroring Baileys #2201):

  • ClientPayload.userAgent.platform becomes :ANDROID (not :WEB).
  • webInfo is omitted (it's a web-client field).
  • DeviceProps.platformType becomes :ANDROID_PHONE.

Why you'd opt in: an Android-registered session can receive view-once media that a Web session cannot.

Costs / caveats — opt in deliberately:

  • Experimental. This is a newer, less-exercised WhatsApp path; upstream labels it experimental ("use at your own risk"). It may behave unexpectedly or be tightened by WhatsApp.
  • The device shows as a phone/Android client, not a desktop browser, in the user's "Linked devices" list.
  • Default (any non-Android :browser) is unchanged — full Web behaviour, webInfo sent, platform: :WEB. Existing configs are unaffected.

2. App-global config — config :amarula, ...

Two knobs are read from application env instead of per-connection: which backend module is the default for storage and for the retry-cache. They're process-wide policy — set once for the whole app — and any connection can still override them by naming its own {module, opts} in :storage / :retry_cache above. Everything else is per-account and lives on the Conn, not here.

config :amarula, :default_storage_adapter, Amarula.Storage.File
config :amarula, :retry_cache_adapter, Amarula.RetryCache.ETS

These point at behaviours you can implement yourself to decide where this state lives (disk, DETS, Postgres, S3, Redis, …):

  • Amarula.Storage — durable auth/session/mapping state. Losing it means re-pairing from a QR, so back it up; see the callbacks + namespaces there, and docs/GOING_PROD.md for choosing/writing an adapter.
  • Amarula.RetryCache — short-lived sent-message cache for retry/decrypt recovery; safe to lose (it self-heals), so an in-memory ETS default is fine.

Logging

Amarula logs through Elixir's Logger. Almost everything is :debug; only connection lifecycle, pairing, and errors are :info/:warning/:error. To keep your dev console clean, set the global level — or silence Amarula specifically without affecting your own logs:

# your app's config
config :logger, level: :info

# or, mute just Amarula (Elixir 1.13+):
Logger.put_module_level(Amarula.Connection, :warning)

Telemetry (Amarula.Telemetry) is the structured, log-independent way to observe Amarula in production.

Summary

Functions

The default config map (without :profile/:auth/:storage, which are caller-supplied).

Merge config over the defaults (caller values win).

The WhatsApp Web protocol version to present on the wire.

Functions

defaults()

@spec defaults() :: map()

The default config map (without :profile/:auth/:storage, which are caller-supplied).

:version is the pinned [2, 3000, 1042537629] unless the AMARULA_WA_VERSION env var overrides it (see wa_version/0).

merge(config)

@spec merge(map()) :: map()

Merge config over the defaults (caller values win).

wa_version()

@spec wa_version() :: [non_neg_integer()]

The WhatsApp Web protocol version to present on the wire.

Returns the compiled-in pinned default ([2, 3000, 1042537629]) unless the AMARULA_WA_VERSION env var is set to a dotted triple (e.g. "2.3000.1042537629"), which lets a consumer track a newer WhatsApp version without recompiling. A malformed value is ignored (with a warning) and the pinned default is used.