Amarula.MessageSecretStore.ReadOnly (amarula v0.5.2)

View Source

A read-only Amarula.MessageSecretStore adapter backed by your message store.

Use this when your application already persists received messages — which you must, to render an edit at all. Pointing the store at that data means Amarula keeps no second copy (no ETS table, no TTL to size) and, unlike the default in-memory adapter, message edits keep working across a connection restart because retention is your durable store's, not a process's lifetime.

You own writes; Amarula only reads. This adapter implements no write side — it does not define the optional put/4 callback, so Amarula never attempts a write. When an edit arrives, Amarula calls your get with the target message's msg_id and expects that message's secret (and its original sender) back.

Usage

Pass a :get function fn profile, msg_id -> {:ok, entry} | :error end, where entry is %{secret: binary(), sender: String.t()}:

Amarula.new(%{
  profile: :primary,
  message_secret_store:
    {Amarula.MessageSecretStore.ReadOnly,
     get: fn _profile, msg_id ->
       case MyApp.Messages.fetch(msg_id) do
         {:ok, %{message_secret: secret, sender_jid: sender}}
         when is_binary(secret) ->
           {:ok, %{secret: secret, sender: sender}}

         _ ->
           :error
       end
     end}
})

:sender must be the message's original, server-attested sender (the bare normalized JID Amarula delivered it from), not one taken from the edit itself — it is what the author check compares against to reject a forged edit. Return :error if you no longer have the message (past your retention); Amarula then can't decrypt the edit, the same outcome as an in-memory cache miss.

Why a function, not a module

For the common "wrap my store" case a closure is the whole adapter. If you need more (per-profile state, a pooled connection), implement the Amarula.MessageSecretStore behaviour directly instead — this module is just the convenient shape.