feat(auth): detect session expiry live and offer re-login in place

Every response already funnels through runtimeFetch, so a classifier there
spots 401s, confirms them against /auth/session (a proxied provider 401
must not read as a logout), and flips a small auth-session store. The web
and hosted surfaces show a frosted banner under the header whose Log in
button hands off to the session gate's existing unlock flow; sends are
paused while expired, the session-load error screen explains the auth case
and retries itself after login, and returning to a long-idle window
revalidates once via visibility/focus. Native mobile feeds the same signal
into its connection re-probe instead of showing the banner; VS Code is
exempt.
This commit is contained in:
Bohdan Triapitsyn
2026-08-26 18:22:04 +03:00
parent 5612849bd7
commit f7a006dc6a
19 changed files with 322 additions and 16 deletions
+5 -1
View File
@@ -2140,7 +2140,8 @@ export const dict: Record<I18nKey, string> = {
'chat.btw.toast.promoteFailed': 'Не вдалося залишити сесію btw',
"chat.container.readOnlySubagentPromptBanner": "Сесії субагентів не можна запитувати.",
"chat.container.sessionLoadError.title": "Не вдалося завантажити сесію",
"chat.container.sessionLoadError.description": "Перевірте з’єднання та спробуйте завантажити цю сесію ще раз.",
"chat.container.sessionLoadError.description": "Не вдалося отримати розмову — сервер може бути вимкнений або недосяжний. Нічого не втрачено; спробуй знову, коли він повернеться.",
"chat.container.sessionLoadError.authDescription": "Сесія завершилась, тож сервер відхилив запит. Увійди — і розмова завантажиться.",
"chat.container.sessionLoadError.retry": "Спробувати знову",
"sessions.sidebar.group.empty.loadingSessions": "Завантаження сесій…",
"sessions.sidebar.group.empty.loadFailed": "Не вдалося оновити сесії.",
@@ -2673,6 +2674,9 @@ export const dict: Record<I18nKey, string> = {
"sessionAuth.error.passkeySignInCanceled": "Вхід за ключем доступу скасовано.",
"sessionAuth.error.enterPasswordForPasskey": "Введіть пароль, щоб додати ключ доступу.",
"sessionAuth.locked.tunnelTitle": "Потрібен доступ до тунелю",
"sessionAuth.expired.banner": "Сесія завершилась — увійди, щоб продовжити.",
"sessionAuth.expired.loginAction": "Увійти",
"sessionAuth.expired.sendBlocked": "Сесія завершилась — увійди, щоб надсилати повідомлення.",
"sessionAuth.locked.unlockTitle": "Розблокувати OpenChamber",
"sessionAuth.locked.tunnelDescription": "Відкрийте цей тунель за допомогою одноразового посилання для з’єднання з настільної програми.",
"sessionAuth.locked.passwordDescription": "Ця сесія захищена паролем.",