Group B of the VK integration — the contour-verified follow-up to the launch+auth MVP: - Share/copy inside the VK iframe go through VK Bridge: the friend-code invite shares via VKWebAppShare and copies via VKWebAppCopyText, since navigator.share is absent in the desktop iframe and navigator.clipboard is blocked there. - The invite link is a VK Mini App direct link (vk.com/app<id>#f<code>) on VK instead of the Telegram link; the app id comes from vk_app_id in the launch params (no build arg needed). The recipient's launch routes the deep link from VK's `hash` launch query parameter. - The app's "auto" theme follows the VK client's light/dark appearance (VKWebAppUpdateConfig), which the VK mobile webview's prefers-color-scheme does not track. - The safe-area CSS vars default to env(safe-area-inset-*), so the VK mobile layout clears the home bar (and Capacitor/PWA too); Telegram still overrides them from its SDK. vk.ts adds vkAppId/vkStartParam/vkShare/vkCopyText/vkOnScheme. Verified: svelte-check, 347 unit (+ vkAppId/vkStartParam/vkShareLink), build, bundle-gate. The VK-Bridge behaviours need the live contour (not reproducible headless).
11 KiB
VK Mini App / VK Games — integration reference
Captured research + our implementation map, so a future session does not need to re-fetch
the VK docs. Authoritative external source: https://dev.vk.com/ (the dev.vk.com portal
does not render via plain HTTP fetch; the facts below were cross-checked against the VKCOM
reference repos cited at the end and verified against our own Go implementation).
A VK game is technically a VK Mini App: an HTML5 SPA VK loads in an iframe inside
vk.com (desktop + mobile web) and in a WebView inside the VK mobile apps (iOS/Android).
We serve our existing SPA under a dedicated /vk/ path, mirroring the Telegram /telegram/
entry — the single-origin, path-routed model.
1. Embedding model
- VK loads the app at the Web iframe URL configured in the app settings (HTTPS + valid cert required), appending the signed launch parameters as the URL query string.
- An optional separate Mobile iframe URL is used by the VK mobile apps (we use the same).
- No special
X-Frame-Options/ CSPframe-ancestorsis required from us — VK frames the configured origin. (Our edge sets no framing headers today, so VK works as-is; see the clickjacking note in §Security.) - URL must match the settings exactly (scheme, host, no stray
www/whitespace).
2. Launch parameters (URL query)
VK appends these to the iframe src. The vk_* set is what the signature covers.
| Param | Meaning |
|---|---|
vk_user_id |
signed-in VK user numeric id — the identity |
vk_app_id |
our registered app id |
vk_is_app_user |
0/1 — user authorized/installed the app |
vk_are_notifications_enabled |
0/1 |
vk_language |
2-letter UI language (ru, en, …) |
vk_platform |
mobile_iphone | mobile_android | mobile_web | desktop_web | … |
vk_ts |
unix seconds when VK generated the params |
vk_ref |
where the app was opened from (catalog, feed, …) |
vk_access_token_settings |
comma-separated granted scopes (often empty) |
vk_group_id, vk_viewer_group_role, vk_is_favorite, vk_client |
optional/contextual |
sign |
the signature (see §3) — NOT part of the signed set |
Always present: vk_user_id, vk_app_id, vk_platform, vk_ts, sign.
The user's name is NOT in the launch params (only vk_user_id). Read it client-side via
VKWebAppGetUserInfo (see §4) — unsigned, so treat it as a cosmetic display seed only.
3. Signature verification (sign) — CONFIRMED base64url, not hex
Algorithm (verified against our gateway/internal/vkauth + an independent Python reference):
- Collect the query params whose key starts with
vk_(excludesign). - Sort by key (alphabetical).
- Serialize as a URL-encoded query string
k=v&k=v…(Gourl.Values.Encode()matches VK's reference serialization for the constrained launch-param charset). HMAC-SHA256(serialized, secret)wheresecret= the app's «Защищённый ключ» (protected / secure key, a.k.a. client_secret) from the app settings.- base64url, no padding (
+→-,/→_, strip=). - Constant-time compare against
sign.
VK launch params have no built-in expiry (unlike Telegram's auth_date). We do NOT enforce
freshness — the minted server session is the short-lived credential; a replay only
re-authenticates the same vk_user_id.
Verified against the official doc https://dev.vk.com/ru/mini-apps/development/launch-params-sign
(prose + PHP example: base64url = strtr('+/','-_') + rtrim('=')) and reproduced identically by
independent Node crypto + Python references. Doc-example caveat: that page shows secret
wvl68m4dR1UpLrVRli → sign exTIBP…, but the secret is a placeholder — recomputing with it does
NOT yield the shown sign (it was made with the real, unshown key). Don't chase the mismatch; our
vkauth.Verify is correct (gateway/internal/vkauth/vkauth_test.go carries cross-checked vectors,
incl. the %2C comma case for vk_access_token_settings).
4. VK Bridge (client SDK)
@vkontakte/vk-bridge (npm, v3.x; bundled — default export bridge). Methods we use / may use:
VKWebAppInit— required: tells VK the Mini App loaded (dismisses VK's loading cover).VKWebAppGetUserInfo—{ id, first_name, last_name, photo_200, … }; no extra scope needed.VKWebAppGetLaunchParams— parsedvk_*withoutsign(so NOT usable for our server verification — readwindow.location.searchinstead, which carriessign).VKWebAppGetAuthToken— OAuth access token for VK API calls (only if we ever call VK API).VKWebAppShare— native share dialog (the friend-code invite uses it;navigator.shareis absent in the desktop VK iframe). Used.VKWebAppCopyText— clipboard copy that works inside the VK iframe, wherenavigator.clipboardis blocked. Used as the copy-code / copy-link path.VKWebAppUpdateConfig(subscribe) — light/dark scheme; the app follows it while the theme pref is "auto" (the VK webview's prefers-color-scheme does not track it). Used.VKWebAppSetViewSettings/VKWebAppSetSwipeSettings— viewport / swipe-back (mobile); not used — the bottom home-bar safe area is handled by CSSenv(safe-area-inset-*)(viewport-fit=cover).
The bridge talks to the embedding VK client over postMessage; it is NOT an external fetch, so it has no telegram.org-style load-hang risk. The SDK reads browser globals at import — we import it lazily so the pure URL helpers stay node-test-importable.
Deep links (direct-link launch): https://vk.com/app<id>#<payload> forwards everything after the
# to the app as the hash launch query parameter (it is also in location.hash, but that
collides with our hash router, so read the hash query param). ? query strings are NOT supported
for VK direct links. The friend-code invite is built as vk.com/app<id>#f<code> — the app id comes
from vk_app_id in the launch params (no VITE_VK_APP_ID needed) — and read back via vkStartParam.
5. Test mode (to verify before moderation)
- App already registered (we have the App ID).
- In the app settings (dev.vk.com /
vk.com/editapp?act=settings&app_id=<id>):- Category = Игра (Game).
- Web iframe URL = our public HTTPS
/vk/(the test-contour origin for contour testing, prodhttps://erudit-game.ru/vk/later). Mobile iframe URL = same. - Copy the «Защищённый ключ» → set as
GATEWAY_VK_APP_SECRET(GiteaTEST_/PROD_secret). - Add own VK id to testers; open in test mode.
- Test mode = visible only to admins/testers, no payments processed.
6. Auth / identity (our model)
vk_user_id(from verified params) → backend identitykind='vk',external_id=vk_user_id, auto-confirmed (a platform identity). First contact seeds language fromvk_languageand the display name from the client-suppliedVKWebAppGetUserInfoname (placeholder if empty).- No VK access token / VK API call needed for the launch+login MVP.
7. Payments / monetization
VK Pay / «голоса» (votes) are optional, not required to publish a free game. Not planned.
8. ToS / moderation (pre-publish, analyzed — no blocker for a free «Эрудит»)
- Trademark: "Scrabble" is trademarked. Our public brand is «Эрудит» (erudit-game.ru), a generic Russian word-game name → fine. Ensure the VK-registered app name is «Эрудит»/word-game, NOT "Scrabble". The repo name is internal and irrelevant to moderation.
- Pre-publish requirements: public Privacy Policy + ToS URLs (disclose collected data:
vk_user_id, language; mention VK), age rating (likely 6+/12+), icon, description. - Dictionary: standard word lists; VK may expect offensive-word filtering — likely fine for a dictionary game, flag if moderation asks.
- In-game chat (UGC): we already have a moderated chat + support relay → covered.
- Moderation reviews after submission (commonly ~24–72h); rejects on violence/hate/sexual/illegal content or IP infringement — none apply.
9. Platforms
Desktop web (iframe), mobile web (iframe), VK iOS app (WKWebView), VK Android app (WebView). Bridge methods behave per-platform; the app's own back chevron + app-shell document-pin cover navigation without VK-specific code. Theme/viewport fine-tuning is best verified live in the real VK client (not reproducible in Playwright — like the iOS gesture caveats).
10. Our implementation map (what to touch for VK)
- Wire:
pkg/fbs/scrabble.fbs→VKLoginRequest{ params, browser_tz, display_name }(regen:make -C pkg fbs+pnpm -C ui codegen). - Gateway:
internal/vkauth/(the §3 verify),internal/transcodeopauth.vk(registered viaWithVKAuth(secret)option;DomainCode→invalid_vk_params),internal/backendclientVKAuth→POST /api/v1/internal/sessions/vk, configGATEWAY_VK_APP_SECRET, SPA mount/vk/ininternal/connectsrv/server.go. - Backend:
internal/accountKindVK+ProvisionVK/vkSeed+confirmedfor platform kinds;internal/server/handlers_auth.gohandleVKAuth+ route; migration00005_vk_identity.sql(widenidentities_kind_chkto include'vk', expand-contract). - UI:
src/lib/vk.ts(onVKPath/vkLaunchParams/insideVK/vkInit/vkUserNameplusvkAppId/vkStartParam/vkShare/vkCopyText/vkOnScheme),app.svelte.tsbootVK(+ deep-link routing and VK scheme→theme) + the/vk/dispatch branch + sharedretryMiniAppBoot,codec.tsencodeVKLogin,transport.ts/client.ts/mock/client.tsauthVK,deeplink.tsvkShareLink,Friends.svelte(VK share/copy),app.css--tg-safe-*defaulting toenv(safe-area-inset-*). - Edge/deploy:
deploy/caddy/Caddyfile/vkpath;GATEWAY_VK_APP_SECRETindocker-compose.yml+.env.example+ci.yaml(TEST_…secret) +prod-deploy.yaml(PROD_…secret, deploy-main). - Deferred: payments (VK Pay / votes), native (Capacitor) VK, account-linking a vk identity to an existing account, VK push. (Done after the launch+auth MVP — Group B: native share + clipboard via the bridge, the friend-code deep link, the auto-theme follow, and the home-bar safe area.)
Sources
- VKCOM/vk-bridge — https://github.com/VKCOM/vk-bridge
- VKCOM/vk-apps-launch-params (canonical signature examples) — https://github.com/VKCOM/vk-apps-launch-params
- kravetsone/vk-launch-params — https://github.com/kravetsone/vk-launch-params
- SevereCloud/vksdk
vkapps.ParamsVerify(Go reference) — https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/vkapps - VK Mini Apps API — https://github.com/VKCOM/vk-mini-apps-api