Управление токенами
===================

AmoCRM выдаёт два токена при OAuth-авторизации:

* **access_token** — срок жизни 24 часа.
* **refresh_token** — срок жизни 3 месяца; обновляется при каждом refresh.

Реактивное обновление по 401
-----------------------------

Это базовое поведение SDK: если API вернул ``401 Unauthorized``, клиент
автоматически вызывает :meth:`~amocrm.client.AmoCRM._refresh_tokens` и
повторяет исходный запрос один раз.

Проактивное обновление по истечению срока
------------------------------------------

При каждом запросе клиент проверяет, не истёк ли (или не истекает ли
в ближайшие 60 секунд) access_token. Если да — refresh выполняется
*до* отправки запроса, без лишнего 401-цикла.

Срок жизни токена определяется так:

1. При инициализации клиента из **JWT-payload** access_token — поле ``exp``
   декодируется без проверки подписи.
2. После каждого refresh — из поля ``expires_in`` в ответе AmoCRM API
   (fallback: 86 400 с / 24 ч).

.. code-block:: text

   _request() вызван
       │
       ├─ expires_at known AND now ≥ expires_at − 60s → _refresh_tokens()
       │
       └─ session.request(...)
               │
               ├─ 401 → _refresh_tokens() → повтор запроса
               └─ OK  → вернуть ответ

Хранилище токенов
-----------------

Интерфейс :class:`~amocrm.auth.TokenStorage` остался неизменным —
``save(access_token, refresh_token)`` и ``load() → tuple[str, str]``.
Время истечения хранится только в памяти клиента и восстанавливается
из JWT при каждом создании нового экземпляра :class:`~amocrm.client.AmoCRM`.

Встроенные реализации хранилища:

.. list-table::
   :header-rows: 1
   :widths: 30 70

   * - Класс
     - Описание
   * - :class:`~amocrm.auth.InMemoryTokenStorage`
     - Хранит токены в атрибутах объекта. Подходит для скриптов и тестов.
   * - :class:`~amocrm.auth.DjangoTokenStorage`
     - Хранит токены в полях модели Django и вызывает ``instance.save()``.
