# `ExWapp.Signal.SessionStore`
[🔗](https://github.com/elchemista/ex_wapp/blob/v0.1.5/lib/ex_wapp/signal/session_store.ex#L1)

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.

# `pending_prekey`

```elixir
@type pending_prekey() :: %{
  :base_key =&gt; binary(),
  :signed_prekey_id =&gt; non_neg_integer(),
  optional(:prekey_id) =&gt; non_neg_integer() | nil
}
```

# `t`

```elixir
@type t() ::
  %{
    sessions: %{required(String.t()) =&gt; map()},
    pending_prekeys: %{required(String.t()) =&gt; map()},
    session_touched_at_ms: %{required(String.t()) =&gt; integer()}
  }
  | ExWapp.Signal.SessionStore.Ref.t()
```

# `attach`

```elixir
@spec attach(term()) :: t()
```

Attaches to an existing ETS-backed session store table.

# `copy_session`

```elixir
@spec copy_session(t(), String.t(), String.t()) :: t()
```

Copies a device session and its pending pre-key state to a PN/LID alias.

# `decrypt_message`

```elixir
@spec decrypt_message(t(), String.t(), binary(), boolean(), map(), map(), keyword()) ::
  {:ok, binary(), t()} | {:error, term()}
```

Decrypts a message from a sender.

With `stateless: true`, prekey messages are decrypted against a temporary
copy of the sender's session. No session, alias or pending prekey is changed.

# `delete_pending_prekey`

```elixir
@spec delete_pending_prekey(t(), String.t()) :: t()
```

Removes the pending outbound pre-key wrapper for one device session.

# `delete_session`

```elixir
@spec delete_session(t(), String.t()) :: t()
```

Removes a session for a recipient JID.

# `delete_sessions_for_user`

```elixir
@spec delete_sessions_for_user(t(), String.t(), map()) :: t()
```

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.

# `encrypt_message`

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

# `evict_one_way_sessions`

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

# `get_or_create_session`

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

# `get_or_create_session`

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

# `get_pending_prekey`

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

# `get_session`

```elixir
@spec get_session(t(), String.t()) :: {:ok, map()} | :not_found
```

Gets a session for a recipient JID.

# `has_session?`

```elixir
@spec has_session?(t(), String.t()) :: boolean()
```

Checks if we have a session for a recipient.

# `list_sessions`

```elixir
@spec list_sessions(t()) :: [String.t()]
```

Lists all JIDs with active sessions.

# `new`

```elixir
@spec new() :: t()
```

Creates a new empty session store.

# `new_ets`

```elixir
@spec new_ets(t() | nil) :: t()
```

Creates a lightweight ETS-backed session store reference.

# `put_pending_prekey`

```elixir
@spec put_pending_prekey(t(), String.t(), pending_prekey()) :: t()
```

Stores the pending outbound pre-key wrapper for one device session.

# `put_session`

```elixir
@spec put_session(t(), String.t(), map()) :: t()
```

Stores a session for a recipient JID.

# `replace`

```elixir
@spec replace(t(), t() | nil) :: t()
```

Replaces the backend contents with the provided snapshot and returns the store.

# `session_touched_at`

```elixir
@spec session_touched_at(t(), String.t()) :: integer() | nil
```

Returns the last-touch timestamp for a stored session, in wall-clock milliseconds.

# `snapshot`

```elixir
@spec snapshot(t() | nil) :: map() | nil
```

Returns a plain snapshot map for persistence/debugging.

# `stale_or_missing?`

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

# `store?`

```elixir
@spec store?(term()) :: boolean()
```

Returns true when the given value is a valid session-store backend.

# `update_session`

```elixir
@spec update_session(t(), String.t(), map()) :: t()
```

Updates a session after encryption/decryption.

---

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