diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 182212f..c326ad2 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -239,7 +239,13 @@ arrive from a platform rather than completing a mandatory registration). development log mailer when no relay is configured) and, once verified, attaches a confirmed email identity. Sends are throttled per recipient (a 60-second cooldown and a five-per-hour cap). Links in the email are built from - a configured public base URL, never a request Host header (anti-injection). An + a configured public base URL, never a request Host header (anti-injection). The email + also carries a **one-tap confirm deeplink** (`/app/#/confirm/`, an opaque + 256-bit token stored only as its SHA-256, 12-hour TTL): a login mints a session in the + browser that opens it (magic-link), a link confirms the identity and emits a `notify` + profile-refresh to the in-app session, and a would-be merge is deferred to the + interactive flow. The confirm page is prefetch-safe — it confirms only on a button + press, so a mail scanner's GET does not consume the single-use token. An **email-login** account is created flagged `is_guest` and stays reapable until the code is confirmed — so an abandoned, never-confirmed login frees its reserved address — with confirming clearing the flag. Accounts and identities use @@ -933,7 +939,10 @@ edge-pause, scroll speed, and the fade-out → gap → fade-in transition) are o eligibility inputs (grants hints, grants/revokes `no_banner`; a future payment flow sets `paid_account`), the backend emits a `notify` **`banner`** sub-kind (a payload-free re-poll signal), and the open client re-fetches `profile.get` to show or hide the banner in place. Operator *content* -edits take effect on the next `profile.get` (open/reconnect/foreground), not mid-session. +edits take effect on the next `profile.get` (open/reconnect/foreground), not mid-session. The same +mechanism carries a **`profile`** sub-kind — a payload-free re-fetch signal emitted when a viewer's +own account changed out of band (an email confirmed through the one-tap deeplink opened in another +browser), so an open in-app session reflects it at once. > A single `app.load` bootstrap aggregator (collapsing `profile.get` + lobby + badge fetches into > one round-trip) was **considered and deferred**: client↔gateway is HTTP/2 (h2c), so the bootstrap diff --git a/docs/FUNCTIONAL.md b/docs/FUNCTIONAL.md index 09dfed6..abafd3c 100644 --- a/docs/FUNCTIONAL.md +++ b/docs/FUNCTIONAL.md @@ -66,7 +66,10 @@ client's light/dark scheme, and the layout clears the VK mobile home bar. The sa is provisioned on the first request but only becomes a durable account once the code is confirmed, so an abandoned, never-confirmed attempt is cleaned up and its address freed. Sends are rate-limited (a short cooldown between codes and a small hourly cap -per address), so a mistyped address or an impatient tap cannot flood an inbox. +per address), so a mistyped address or an impatient tap cannot flood an inbox. The email +also carries a **one-tap link** that confirms the address — or signs the player straight +in — in a single tap; opening it in another browser reflects in the app at once, and a +mail scanner that merely fetches the link never triggers it (it acts only on a button press). Telegram runs a **single bot**: every player uses the same bot, and all of its chat and out-of-app notifications are written in the player's own **interface language** (en/ru). A separate optional **promo bot** can run alongside the diff --git a/docs/FUNCTIONAL_ru.md b/docs/FUNCTIONAL_ru.md index 1ca51cd..690ddfb 100644 --- a/docs/FUNCTIONAL_ru.md +++ b/docs/FUNCTIONAL_ru.md @@ -71,7 +71,10 @@ launch-параметрам VK (их проверяет gateway), и при пе запросе, но становится постоянным аккаунтом только после подтверждения кода — так брошенная, неподтверждённая попытка вычищается, а адрес освобождается. Отправки ограничены по частоте (короткая пауза между кодами и небольшой часовой лимит на адрес), чтобы опечатка в адресе или нетерпеливый тап -не завалили почтовый ящик. +не завалили почтовый ящик. В письме также есть **ссылка одного нажатия**, которая подтверждает +адрес — или сразу выполняет вход — в один тап; открытие её в другом браузере тут же отражается в +приложении, а почтовый сканер, который просто загружает ссылку, её не срабатывает (действие +только по нажатию кнопки). Telegram держит **единого бота**: все игроки пользуются одним и тем же ботом, а весь его чат и внеприложенческие уведомления пишутся на **языке интерфейса** самого игрока (en/ru). Рядом с основным может работать отдельный опциональный diff --git a/ui/src/lib/codec.test.ts b/ui/src/lib/codec.test.ts index 21b37ae..4e1b1ab 100644 --- a/ui/src/lib/codec.test.ts +++ b/ui/src/lib/codec.test.ts @@ -21,6 +21,7 @@ import { decodeStats, encodeCheckWord, encodeEmailRequest, + encodeEmailConfirmLink, encodeFeedbackSubmit, encodeDraftSave, encodeEnqueue, @@ -121,6 +122,12 @@ describe('codec', () => { ); expect(email.email()).toBe('a@example.com'); expect(email.browserTz()).toBe('+00:00'); + expect(email.language()).toBe('en'); + + const confirm = fb.EmailConfirmLinkRequest.getRootAsEmailConfirmLinkRequest( + new ByteBuffer(encodeEmailConfirmLink('tok-abc')), + ); + expect(confirm.token()).toBe('tok-abc'); const vk = fb.VKLoginRequest.getRootAsVKLoginRequest( new ByteBuffer(encodeVKLogin('vk_user_id=494075&vk_ts=1&sign=abc', '+03:00', 'Иван Петров')),