Продление сессии: приложение подхватывает свежий токен из ответа

Бэкенд с пятого дня жизни токена кладёт продлённый в заголовок X-Driver-Token
(crm2, modules/payments/driver_auth). Здесь — подмена: тридцать дней начинают
считаться от последнего захода, а не от входа, и водитель перестаёт раз в месяц
упираться в экран входа.

Подменяем только тот токен, с которым сами ушли. Сеанс мог смениться, пока
запрос был в пути — водитель вышел, сотрудник начал просмотр от чужого лица, —
и продление чужого ответа вернуло бы закрытый сеанс к жизни. Просмотр от лица
водителя не продлеваем: его восемь часов сознательные.

README: приложение запущено для всех 22.08.2026, гейт по номерам снят. Раздел
оставлен как инструкция на случай, если вход снова понадобится сузить.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 22:19:42 +10:00
co-authored by Claude Opus 5
parent 00006ed51b
commit 80ac25ee0f
3 changed files with 85 additions and 4 deletions
+24
View File
@@ -7,6 +7,29 @@ export const API_BASE = import.meta.env.VITE_API_BASE ?? "https://crm.pptaxi.ru/
/** Засечка времени на конкретный запрос. WeakMap — чтобы не держать Request'ы. */
const started = new WeakMap<Request, number>();
/**
* Заголовок, которым бэкенд отдаёт ПРОДЛЁННЫЙ токен водителя (см. driver_auth.py).
*
* Токен живёт 30 дней, и без продления они считались бы от входа: раз в месяц
* водителя выкидывало бы на экран входа, а «войти» для него означает сходить в
* бота за кодом. Начиная с пятого дня жизни токена бэкенд кладёт в ответ свежий,
* мы его молча подменяем — и 30 дней начинают считаться от последнего захода.
*/
const RENEWED_TOKEN_HEADER = "X-Driver-Token";
/** Подменить токен на продлённый, если ответ его принёс. */
function adoptRenewedToken(request: Request, response: Response): void {
const fresh = response.headers.get(RENEWED_TOKEN_HEADER);
if (!fresh) return;
const auth = useAuth.getState();
// Сеанс мог смениться, пока запрос был в пути: водитель вышел, сотрудник начал
// просмотр от чужого лица. Подменяем только тот токен, с которым сами ушли, —
// иначе продление чужого ответа вернёт закрытый сеанс к жизни.
if (auth.impersonated || auth.driverId == null) return;
if (request.headers.get("Authorization") !== `Bearer ${auth.token}`) return;
auth.setSession(fresh, auth.driverId);
}
export const api: KyInstance = ky.create({
prefixUrl: API_BASE,
timeout: 30_000,
@@ -30,6 +53,7 @@ export const api: KyInstance = ky.create({
status: response.status,
ms: Math.round(performance.now() - (started.get(request) ?? performance.now())),
});
adoptRenewedToken(request, response);
},
],
beforeError: [