Manages Signal sessions for multiple recipients.
Sessions are stored per (user, device) pair and persisted to the main store.
Uses ExWapp.Signal.Adapter to switch between Signal implementations.
Summary
Functions
Attaches to an existing ETS-backed session store table.
Copies a device session and its pending pre-key state to a PN/LID alias.
Decrypts a message from a sender.
Removes the pending outbound pre-key wrapper for one device session.
Removes a session for a recipient JID.
Deletes cached sessions for the target JID and any PN<->LID aliases derived from the current mapping store. Matching is done on the stripped base JID so all device variants are cleared together.
Encrypts a message to a recipient.
Deletes sessions this device has only ever sent on.
Creates or gets a session for a recipient.
Creates or gets a session for a recipient with options.
Gets the pending outbound pre-key wrapper for a device session.
Gets a session for a recipient JID.
Checks if we have a session for a recipient.
Lists all JIDs with active sessions.
Creates a new empty session store.
Creates a lightweight ETS-backed session store reference.
Stores the pending outbound pre-key wrapper for one device session.
Stores a session for a recipient JID.
Replaces the backend contents with the provided snapshot and returns the store.
Returns the last-touch timestamp for a stored session, in wall-clock milliseconds.
Returns a plain snapshot map for persistence/debugging.
Returns true when none of the candidate JIDs has a recent session touch timestamp.
Returns true when the given value is a valid session-store backend.
Updates a session after encryption/decryption.
Types
@type pending_prekey() :: %{ :base_key => binary(), :signed_prekey_id => non_neg_integer(), optional(:prekey_id) => non_neg_integer() | nil }
Functions
Attaches to an existing ETS-backed session store table.
Copies a device session and its pending pre-key state to a PN/LID alias.
@spec decrypt_message(t(), String.t(), binary(), boolean(), map(), map()) :: {:ok, binary(), t()} | {:error, term()}
Decrypts a message from a sender.
Removes the pending outbound pre-key wrapper for one device session.
Removes a session for a recipient JID.
Deletes cached sessions for the target JID and any PN<->LID aliases derived from the current mapping store. Matching is done on the stripped base JID so all device variants are cleared together.
@spec encrypt_message(t(), String.t(), binary(), map(), non_neg_integer(), map()) :: {:ok, binary(), :prekey | :whisper, t()} | {:error, term()}
Encrypts a message to a recipient.
Creates a session if needed using the provided prekey bundle. Returns either a PreKeySignalMessage (new session) or regular SignalMessage.
@spec evict_one_way_sessions(t(), non_neg_integer(), integer()) :: {t(), [String.t()]}
Deletes sessions this device has only ever sent on.
A session with traffic in one direction and nothing coming back is the shape a
desync leaves behind: the peer dropped its half and re-initiated, this device
never managed to adopt the new one, and every send since has been encrypted
against a ratchet the peer abandoned. The server answers those with
<ack error="463"> forever, because nothing in the send path ever concludes
that the session itself is the problem.
It is also the shape of a perfectly healthy session to someone who has not
replied yet, so age is what tells them apart — hence min_age_ms, and hence
calling this at connect rather than mid-conversation. An unstamped session is
treated as old: timestamps are written on every store, so their absence means
the session outlived the process that made it.
Returns the store and the JIDs that were dropped. The next send to those peers fetches a fresh bundle and negotiates from scratch.
@spec get_or_create_session(t(), String.t(), map(), map(), non_neg_integer()) :: {:ok, map(), binary() | nil, t()} | {:error, term()}
Creates or gets a session for a recipient.
If no session exists, uses the prekey bundle to create one.
@spec get_or_create_session( t(), String.t(), map(), map(), non_neg_integer(), keyword() ) :: {:ok, map(), binary() | nil, t()} | {:error, term()}
Creates or gets a session for a recipient with options.
Options
:force- force creation from the supplied bundle even if a session exists.
@spec get_pending_prekey(t(), String.t()) :: {:ok, pending_prekey()} | :not_found
Gets the pending outbound pre-key wrapper for a device session.
A pending wrapper remains active until that same device proves the Signal session by sending a message that decrypts successfully.
Gets a session for a recipient JID.
Checks if we have a session for a recipient.
Lists all JIDs with active sessions.
@spec new() :: t()
Creates a new empty session store.
Creates a lightweight ETS-backed session store reference.
@spec put_pending_prekey(t(), String.t(), pending_prekey()) :: t()
Stores the pending outbound pre-key wrapper for one device session.
Stores a session for a recipient JID.
Replaces the backend contents with the provided snapshot and returns the store.
Returns the last-touch timestamp for a stored session, in wall-clock milliseconds.
Returns a plain snapshot map for persistence/debugging.
@spec stale_or_missing?(t(), [String.t()], pos_integer(), integer()) :: boolean()
Returns true when none of the candidate JIDs has a recent session touch timestamp.
Returns true when the given value is a valid session-store backend.
Updates a session after encryption/decryption.