feat(vk): embed the game as a VK Mini App
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 1m0s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 1m0s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s
Mirror the Telegram Mini App wrapper for VK: the SPA loads at a new /vk/ entry, authenticates from VK's signed launch parameters, and provisions a 'vk' platform identity — the minimum to run the game in VK test mode. - Gateway verifies the launch signature in-process (internal/vkauth: HMAC-SHA256 over the sorted vk_* params under GATEWAY_VK_APP_SECRET, base64url) — a pure offline check, no side-service. New auth.vk op (gated on the secret), backendclient.VKAuth, /vk/ SPA mount. - Backend: KindVK + ProvisionVK/vkSeed, /sessions/vk handler, identity kind widened to include 'vk' (migration 00005, expand-contract). - UI: src/lib/vk.ts (VK Bridge, lazy-imported), bootVK + the /vk/ boot dispatch, encodeVKLogin + authVK across transport/client/mock. VK omits the name from the signed params, so the client reads it via VKWebAppGetUserInfo as an unsigned display seed. - Deploy: /vk in the edge Caddyfile, GATEWAY_VK_APP_SECRET wired through compose + .env.example + CI (TEST_) + prod-deploy (PROD_). - Admin console: surface the VK user id (link to the VK profile) next to the Telegram id on the user card. - Docs: ARCHITECTURE §12/§13, FUNCTIONAL (+ _ru), gateway README; VK integration reference under .claude/. Signature algorithm verified against dev.vk.com plus independent Node/Python references and a %2C edge-case vector.
This commit is contained in:
@@ -32,6 +32,7 @@ import {
|
||||
telegramCloudGet,
|
||||
telegramCloudSet,
|
||||
} from './telegram';
|
||||
import { onVKPath, insideVK, vkInit, vkLaunchParams, vkUserName } from './vk';
|
||||
import { CLOUD_PREFS_KEY, decodeClientPrefs, encodeClientPrefs } from './cloudprefs';
|
||||
import { parseStartParam } from './deeplink';
|
||||
import { clearSession, loadPrefs, loadSession, saveSession, savePrefs } from './session';
|
||||
@@ -664,6 +665,17 @@ export async function bootstrap(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
// VK Mini App launch: signal readiness to the VK client (which dismisses its loading cover), then
|
||||
// authenticate from the signed launch parameters in the URL — the display name comes from
|
||||
// VKWebAppGetUserInfo, since VK omits it from the signed params. The /vk/ entry opened outside VK
|
||||
// (no signed params — e.g. a developer hitting the URL directly) falls through to the web flow.
|
||||
if (onVKPath() && insideVK()) {
|
||||
await vkInit();
|
||||
await bootVK();
|
||||
app.ready = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const saved = await loadSession();
|
||||
if (saved) {
|
||||
await adoptSession(saved);
|
||||
@@ -674,10 +686,10 @@ export async function bootstrap(): Promise<void> {
|
||||
app.ready = true;
|
||||
}
|
||||
|
||||
// Inside a Mini App the only identity is the Telegram session, so a failed launch must never fall
|
||||
// back to the web login screen. A transient backend outage (a deploy rolling over) is retried a
|
||||
// few times in silence; only then does the boot-error screen surface, from which Retry re-runs the
|
||||
// same path (retryTelegramBoot).
|
||||
// Inside a Mini App the only identity is the platform session (Telegram or VK), so a failed launch
|
||||
// must never fall back to the web login screen. A transient backend outage (a deploy rolling over)
|
||||
// is retried a few times in silence; only then does the boot-error screen surface, from which Retry
|
||||
// re-runs the same path (retryMiniAppBoot).
|
||||
const TELEGRAM_BOOT_RETRIES = 2;
|
||||
const TELEGRAM_BOOT_RETRY_MS = 1200;
|
||||
|
||||
@@ -714,14 +726,48 @@ async function bootTelegram(launch: TelegramLaunch): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* retryTelegramBoot re-attempts the Mini App launch from the boot-error screen's Retry button. It
|
||||
* clears the error and shows the loading state again, then runs the same retrying boot; on success
|
||||
* the app renders normally, otherwise the boot-error screen returns.
|
||||
* bootVK authenticates a VK Mini App launch from the signed launch parameters in the URL, seeding a
|
||||
* brand-new account's display name from VKWebAppGetUserInfo. Like bootTelegram it retries a few
|
||||
* times on a transient failure before raising the boot-error screen, and a blocked account is
|
||||
* terminal. This MVP carries no VK deep-link routing.
|
||||
*/
|
||||
export async function retryTelegramBoot(): Promise<void> {
|
||||
async function bootVK(): Promise<void> {
|
||||
const params = vkLaunchParams();
|
||||
const displayName = await vkUserName();
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
try {
|
||||
await adoptSession(await gateway.authVK(params, displayName));
|
||||
app.bootError = false;
|
||||
return;
|
||||
} catch (err) {
|
||||
if (err instanceof GatewayError && err.code === 'account_blocked') {
|
||||
await enterBlocked();
|
||||
return;
|
||||
}
|
||||
if (attempt >= TELEGRAM_BOOT_RETRIES) {
|
||||
app.bootError = true;
|
||||
return;
|
||||
}
|
||||
await delay(TELEGRAM_BOOT_RETRY_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* retryMiniAppBoot re-attempts a Mini App launch from the boot-error screen's Retry button — the VK
|
||||
* boot on the /vk/ entry, the Telegram boot otherwise. It clears the error and shows the loading
|
||||
* state again, then runs the same retrying boot; on success the app renders normally, otherwise the
|
||||
* boot-error screen returns.
|
||||
*/
|
||||
export async function retryMiniAppBoot(): Promise<void> {
|
||||
app.bootError = false;
|
||||
app.ready = false;
|
||||
await bootTelegram(telegramLaunch());
|
||||
if (onVKPath()) {
|
||||
await vkInit();
|
||||
await bootVK();
|
||||
} else {
|
||||
await bootTelegram(telegramLaunch());
|
||||
}
|
||||
app.ready = true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user