feat(vk): show a launch diagnostic on a direct /vk/ open instead of a guest
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m13s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m7s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m13s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m7s
Opening the dedicated /vk/ entry directly in an ordinary browser (no signed VK
launch) fell through to the web flow and silently started a throwaway guest,
which is wrong for a VK-only entry. Mirror the existing /telegram/ launch-error
behaviour: render a compact, shareable, privacy-safe diagnostic screen instead.
- Boot: a new branch `onVKPath() && !insideVK()` sets app.launchError and stops
the fall-through, the VK counterpart of the /telegram/ diagnostic guard.
- Diagnostic (passive, no VK Bridge round-trip): reads the URL launch-parameter
NAMES (never the `sign` value — auth material), whether the URL was signed,
`vk_platform`, the iframe/referrer context, plus the shared client-environment
lines. VK signs the launch URL at load, so — unlike Telegram's initData — the
parameters never arrive late; hence the VK screen offers Share only, no Retry.
- Generalise the screen: TelegramLaunchError.svelte -> LaunchError.svelte, which
renders a neutral pre-formatted report and a per-platform title, with Retry
gated on a `retry` flag (Telegram true, VK false). app.launchError is now a
neutral LaunchDiag { platform, report, retry }.
- New lib/launchdiag.ts holds the shared pieces both platforms reuse: the
LaunchDiag shape, the client-environment lines, and the query field-NAME
reader (moved out of telegram.ts so VK does not duplicate them).
Tests: pure vkDiagLines units, incl. a guard that the `sign` value never leaks
into the report. i18n: launch.errorTitleVk (en/ru). Docs: FUNCTIONAL (+ru) user
story and ARCHITECTURE entry-path note.
This commit is contained in:
+22
-12
@@ -14,8 +14,7 @@ import { applyReduceMotion, applyTelegramTheme, applyTheme, type ThemePref, type
|
||||
import {
|
||||
insideTelegram,
|
||||
telegramClose,
|
||||
collectTelegramDiag,
|
||||
type TelegramDiag,
|
||||
telegramLaunchDiag,
|
||||
onTelegramPath,
|
||||
hasLaunchFragment,
|
||||
loadTelegramSDK,
|
||||
@@ -33,7 +32,8 @@ import {
|
||||
telegramCloudGet,
|
||||
telegramCloudSet,
|
||||
} from './telegram';
|
||||
import { onVKPath, insideVK, vkInit, vkClose, vkDisableSwipeBack, vkLaunchParams, vkUserName, vkStartParam, vkOnScheme, vkOnInsets, vkSetViewSettings, appearanceForBg } from './vk';
|
||||
import { onVKPath, insideVK, vkLaunchDiag, vkInit, vkClose, vkDisableSwipeBack, vkLaunchParams, vkUserName, vkStartParam, vkOnScheme, vkOnInsets, vkSetViewSettings, appearanceForBg } from './vk';
|
||||
import type { LaunchDiag } from './launchdiag';
|
||||
import { pendingVKLink, type VKLinkCallback } from './vkid';
|
||||
import { isStandalone } from './pwa';
|
||||
import { registerServiceWorker } from './pwa.svelte';
|
||||
@@ -82,11 +82,12 @@ export const app = $state<{
|
||||
* backend was down during a deploy). App.svelte then renders the boot-error retry screen
|
||||
* instead of the web login — a Mini App has no manual sign-in to fall back to. */
|
||||
bootError: boolean;
|
||||
/** On the dedicated /telegram/ entry, set to a privacy-safe diagnostic snapshot when a Mini App
|
||||
* launch carried no sign-in data (empty initData). App.svelte then renders the compact
|
||||
* launch-error screen (screens/TelegramLaunchError) — a shareable probe for why Telegram
|
||||
* delivered no initData (seen on some Android clients) — instead of bouncing to the landing. */
|
||||
launchError: TelegramDiag | null;
|
||||
/** On a dedicated Mini App entry (/telegram/ or /vk/) opened without a valid launch, set to a
|
||||
* privacy-safe diagnostic snapshot. App.svelte then renders the compact, shareable launch-error
|
||||
* screen (screens/LaunchError) — a probe for why Telegram delivered no initData (seen on some
|
||||
* Android clients), or why /vk/ carried no signed launch — instead of bouncing to the landing
|
||||
* (Telegram) or silently proceeding as a guest (VK). */
|
||||
launchError: LaunchDiag | null;
|
||||
/** Whether the hidden on-device debug panel (components/DebugPanel) is open — toggled by tapping
|
||||
* the header title ten times in quick succession. A support aid; carries no secrets. */
|
||||
debugOpen: boolean;
|
||||
@@ -850,7 +851,7 @@ export async function bootstrap(): Promise<void> {
|
||||
// clients), render the compact launch-error screen with a diagnostic snapshot the user can share
|
||||
// with the developer, instead of bouncing the visitor to the marketing landing.
|
||||
if (onTelegramPath() && !insideTelegram()) {
|
||||
app.launchError = collectTelegramDiag();
|
||||
app.launchError = telegramLaunchDiag();
|
||||
app.ready = true;
|
||||
return;
|
||||
}
|
||||
@@ -875,10 +876,19 @@ export async function bootstrap(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
// The dedicated /vk/ entry opened without a signed VK launch (a plain browser tab, or a VK client
|
||||
// that delivered no launch parameters) shows the compact, shareable launch diagnostic instead of
|
||||
// silently proceeding as a guest — the VK counterpart of the /telegram/ launch-error screen. VK puts
|
||||
// its signed parameters in the launch URL at load, so there is nothing to wait for (hence no Retry).
|
||||
if (onVKPath() && !insideVK()) {
|
||||
app.launchError = vkLaunchDiag();
|
||||
app.ready = true;
|
||||
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.
|
||||
// VKWebAppGetUserInfo, since VK omits it from the signed params.
|
||||
if (onVKPath() && insideVK()) {
|
||||
// Follow the VK client's light/dark appearance while the user keeps the app's "auto" theme (the
|
||||
// VK mobile webview's prefers-color-scheme does not track it).
|
||||
@@ -1072,7 +1082,7 @@ export async function retryTelegramLaunch(): Promise<void> {
|
||||
// Re-attempt the SDK load — the network may have recovered since the launch-error screen showed.
|
||||
await loadTelegramSDK(TELEGRAM_SDK_TIMEOUT_MS);
|
||||
if (!insideTelegram()) {
|
||||
app.launchError = collectTelegramDiag();
|
||||
app.launchError = telegramLaunchDiag();
|
||||
return;
|
||||
}
|
||||
app.launchError = null;
|
||||
|
||||
@@ -27,6 +27,7 @@ export const en = {
|
||||
'boot.errorBody': 'Please try again in a moment.',
|
||||
|
||||
'launch.errorTitle': "Can't open in Telegram",
|
||||
'launch.errorTitleVk': "Can't open in VK",
|
||||
'launch.errorBody': 'Screenshot or share this with the developer.',
|
||||
'launch.share': 'Share',
|
||||
'launch.copied': 'Copied',
|
||||
|
||||
@@ -28,6 +28,7 @@ export const ru: Record<MessageKey, string> = {
|
||||
'boot.errorBody': 'Попробуйте ещё раз или зайдите позже.',
|
||||
|
||||
'launch.errorTitle': 'Не открывается в Telegram',
|
||||
'launch.errorTitleVk': 'Не открывается в VK',
|
||||
'launch.errorBody': 'Сделайте скриншот или поделитесь с разработчиком.',
|
||||
'launch.share': 'Поделиться',
|
||||
'launch.copied': 'Скопировано',
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// Shared launch-diagnostic plumbing for the dedicated Mini App entries (/telegram/, /vk/). When one of
|
||||
// those is opened without a valid launch — a plain browser tab, or a client that delivered no sign-in
|
||||
// data — the app renders a compact, privacy-safe diagnostic snapshot (the LaunchError screen) the user
|
||||
// can screenshot or share, instead of silently proceeding (to the marketing landing, or as a guest).
|
||||
// This module holds the pieces both platforms share: the neutral report shape the screen renders, the
|
||||
// client environment lines, and a query-string field-NAME reader (names only — the values can be auth
|
||||
// material). The platform-specific collectors live in telegram.ts / vk.ts.
|
||||
|
||||
/**
|
||||
* LaunchDiag is the neutral, pre-formatted snapshot the LaunchError screen renders, so the screen is
|
||||
* platform-agnostic: it shows the report text and, only when retry is set, a Retry button. The
|
||||
* collectors (telegramLaunchDiag / vkLaunchDiag) own the platform-specific formatting.
|
||||
*/
|
||||
export interface LaunchDiag {
|
||||
/** platform selects the screen's title copy ('telegram' | 'vk'). */
|
||||
platform: 'telegram' | 'vk';
|
||||
/** report is the compact multi-line "key: value" snapshot, sized to fit one screenshot. */
|
||||
report: string;
|
||||
/** retry is whether the screen shows a Retry button — true only where a late-arriving launch can
|
||||
* still succeed (Telegram initData), false where the launch is fixed at load (VK's signed URL). */
|
||||
retry: boolean;
|
||||
}
|
||||
|
||||
interface uaBrand {
|
||||
brand: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
interface uaDataValue {
|
||||
platform?: string;
|
||||
mobile?: boolean;
|
||||
brands?: uaBrand[];
|
||||
}
|
||||
|
||||
/** uaData returns the User-Agent Client Hints object (Chromium only — notably the Android in-app
|
||||
* webviews), or undefined where it is unavailable (iOS / Safari / Firefox). */
|
||||
export function uaData(): uaDataValue | undefined {
|
||||
if (typeof navigator === 'undefined') return undefined;
|
||||
return (navigator as unknown as { userAgentData?: uaDataValue }).userAgentData;
|
||||
}
|
||||
|
||||
/**
|
||||
* clientEnvLines returns the shared client-environment lines of a launch diagnostic — the OS / mobile
|
||||
* flag, the browser brands, and the full User-Agent — read from Client Hints (with a navigator.platform
|
||||
* fallback). Both the Telegram and VK reports append these, so the environment readout stays identical
|
||||
* across platforms. No secrets: it carries only client identification, never an IP.
|
||||
*/
|
||||
export function clientEnvLines(): string[] {
|
||||
const ua = uaData();
|
||||
const navPlatform =
|
||||
typeof navigator === 'undefined' ? '' : ((navigator as unknown as { platform?: string }).platform ?? '');
|
||||
const osPlatform = ua?.platform ?? navPlatform;
|
||||
const mobile = ua?.mobile === undefined ? '' : ua.mobile ? 'yes' : 'no';
|
||||
const browser = (ua?.brands ?? []).map((b) => `${b.brand} ${b.version}`).join(', ');
|
||||
const userAgent = typeof navigator === 'undefined' ? '' : navigator.userAgent;
|
||||
return [`os: ${osPlatform || '—'} mobile: ${mobile || '—'}`, `browser: ${browser || '—'}`, `ua: ${userAgent || '—'}`];
|
||||
}
|
||||
|
||||
/**
|
||||
* queryFieldNames parses a URL query string (or any `key=value&…` form) and returns only its field
|
||||
* NAMES, never the values — the values (a Telegram initData hash, a VK sign) are auth material that must
|
||||
* never surface in a shareable diagnostic. Returns an empty array for empty or unparseable input.
|
||||
*/
|
||||
export function queryFieldNames(raw: string): string[] {
|
||||
if (!raw) return [];
|
||||
try {
|
||||
return [...new URLSearchParams(raw).keys()];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
+24
-46
@@ -4,6 +4,7 @@
|
||||
// the app uses: launch detection, initData (for auth.telegram), the deep-link start parameter,
|
||||
// theme params, and ready()/expand(). Every helper is safe to call outside Telegram.
|
||||
|
||||
import { clientEnvLines, queryFieldNames, type LaunchDiag } from './launchdiag';
|
||||
import type { TelegramThemeParams } from './theme';
|
||||
|
||||
/** TelegramPopupButton is one button of a native showPopup (Bot API 6.2). */
|
||||
@@ -471,24 +472,6 @@ export function hasLaunchFragment(): boolean {
|
||||
* the launch-error screen reports. Only field names are ever inspected, never their values. */
|
||||
const expectedInitDataFields = ['user', 'auth_date', 'hash', 'signature'];
|
||||
|
||||
interface uaBrand {
|
||||
brand: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
interface uaDataValue {
|
||||
platform?: string;
|
||||
mobile?: boolean;
|
||||
brands?: uaBrand[];
|
||||
}
|
||||
|
||||
/** uaData returns the User-Agent Client Hints object (Chromium only — notably the Android Telegram
|
||||
* webview), or undefined where it is unavailable (iOS / Safari / Firefox). */
|
||||
function uaData(): uaDataValue | undefined {
|
||||
if (typeof navigator === 'undefined') return undefined;
|
||||
return (navigator as unknown as { userAgentData?: uaDataValue }).userAgentData;
|
||||
}
|
||||
|
||||
/** launchFragmentData returns the raw tgWebAppData carried in the URL fragment (the form Telegram
|
||||
* appends on launch), or '' when absent. With no SDK present a non-empty value means Telegram
|
||||
* delivered the data but telegram-web-app.js never ran to parse it. */
|
||||
@@ -503,17 +486,6 @@ function launchFragmentData(): string {
|
||||
}
|
||||
}
|
||||
|
||||
/** initDataFieldNames parses a Telegram initData (or raw fragment data) query string and returns
|
||||
* only its field NAMES — never the values, since the hash / signature are auth material. */
|
||||
function initDataFieldNames(raw: string): string[] {
|
||||
if (!raw) return [];
|
||||
try {
|
||||
return [...new URLSearchParams(raw).keys()];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TelegramDiag is a privacy-safe snapshot of why a Mini App launch lacked sign-in data, taken at
|
||||
* the moment of failure and rendered on the launch-error screen so a stuck user can share it with
|
||||
@@ -542,30 +514,20 @@ export interface TelegramDiag {
|
||||
fieldsPresent: string[];
|
||||
/** The expected field names absent from the launch data (a subset of expectedInitDataFields). */
|
||||
fieldsMissing: string[];
|
||||
/** The OS / platform per User-Agent Client Hints (else navigator.platform), e.g. 'Android', ''. */
|
||||
osPlatform: string;
|
||||
/** Whether the client reports itself mobile per Client Hints: 'yes' | 'no' | '' (unknown). */
|
||||
mobile: string;
|
||||
/** The browser brands + major versions per Client Hints (Chromium only), or ''. */
|
||||
browser: string;
|
||||
/** The full User-Agent string — the catch-all that also carries the OS version and webview build. */
|
||||
userAgent: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* collectTelegramDiag captures a TelegramDiag snapshot of the current launch state. Call it at the
|
||||
* point a /telegram/ launch is found to lack sign-in data, so the snapshot reflects that moment —
|
||||
* sign-in data arriving late would otherwise mask the failure.
|
||||
* sign-in data arriving late would otherwise mask the failure. telegramLaunchDiag wraps it into the
|
||||
* neutral LaunchDiag the shared LaunchError screen renders.
|
||||
*/
|
||||
export function collectTelegramDiag(): TelegramDiag {
|
||||
const w = webApp();
|
||||
const sdk = typeof window !== 'undefined' && !!(window as unknown as { Telegram?: unknown }).Telegram;
|
||||
const initData = w?.initData ?? '';
|
||||
const fragData = initData ? '' : launchFragmentData();
|
||||
const present = initDataFieldNames(initData || fragData);
|
||||
const ua = uaData();
|
||||
const navPlatform =
|
||||
typeof navigator === 'undefined' ? '' : (navigator as unknown as { platform?: string }).platform ?? '';
|
||||
const present = queryFieldNames(initData || fragData);
|
||||
return {
|
||||
hasSDK: sdk,
|
||||
hasWebApp: !!w,
|
||||
@@ -576,13 +538,29 @@ export function collectTelegramDiag(): TelegramDiag {
|
||||
hashHadTgData: fragData.length > 0,
|
||||
fieldsPresent: present,
|
||||
fieldsMissing: expectedInitDataFields.filter((f) => !present.includes(f)),
|
||||
osPlatform: ua?.platform ?? navPlatform,
|
||||
mobile: ua?.mobile === undefined ? '' : ua.mobile ? 'yes' : 'no',
|
||||
browser: (ua?.brands ?? []).map((b) => `${b.brand} ${b.version}`).join(', '),
|
||||
userAgent: typeof navigator === 'undefined' ? '' : navigator.userAgent,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* telegramLaunchDiag captures the current Telegram launch state as a neutral LaunchDiag for the shared
|
||||
* LaunchError screen: the platform-specific lines (SDK load, initData length, field names) plus the
|
||||
* shared client-environment lines. retry is true — Telegram's initData can arrive late on a slow
|
||||
* client, so the screen's Retry can still recover the launch.
|
||||
*/
|
||||
export function telegramLaunchDiag(): LaunchDiag {
|
||||
const d = collectTelegramDiag();
|
||||
const report = [
|
||||
`sdk-load: ${d.sdkLoad}`,
|
||||
`sdk: ${d.hasSDK ? 'yes' : 'no'} webapp: ${d.hasWebApp ? 'yes' : 'no'}`,
|
||||
`tg-platform: ${d.platform || '—'} tg-version: ${d.version || '—'}`,
|
||||
`initData: ${d.initDataLen > 0 ? d.initDataLen : 'empty'} tgdata-in-url: ${d.hashHadTgData ? 'yes' : 'no'}`,
|
||||
`fields: ${d.fieldsPresent.join(',') || '—'}`,
|
||||
`missing: ${d.fieldsMissing.join(',') || '—'}`,
|
||||
...clientEnvLines(),
|
||||
].join('\n');
|
||||
return { platform: 'telegram', report, retry: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* telegramChromeDiag returns a compact, privacy-safe readout of Telegram's viewport / chrome state
|
||||
* (platform, version, fullscreen/expanded, viewport geometry, safe-area insets, SDK-load outcome,
|
||||
|
||||
@@ -5,7 +5,9 @@ import {
|
||||
routeExternalLinkInVK,
|
||||
vkAndroidWebView,
|
||||
vkAppId,
|
||||
vkDiagLines,
|
||||
vkExternalBrowserUrl,
|
||||
vkLaunchDiag,
|
||||
vkLaunchParams,
|
||||
vkStartParam,
|
||||
appearanceForBg,
|
||||
@@ -119,6 +121,45 @@ describe('vk external links', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('vkDiagLines (launch diagnostic)', () => {
|
||||
it('reports an unsigned browser open: no sign, every VK param missing', () => {
|
||||
const lines = vkDiagLines('utm_source=catalog', false, '');
|
||||
expect(lines[0]).toBe('launch: unsigned iframe: no');
|
||||
expect(lines[1]).toBe('vk-platform: —');
|
||||
expect(lines[2]).toBe('params: utm_source');
|
||||
expect(lines[3]).toBe('missing: vk_app_id,vk_user_id,vk_ts,vk_platform,sign');
|
||||
expect(lines[4]).toBe('referrer: —');
|
||||
});
|
||||
|
||||
it('reports a signed launch and never leaks the sign value, only its name', () => {
|
||||
const lines = vkDiagLines(
|
||||
'vk_app_id=6736218&vk_platform=mobile_android&vk_user_id=42&vk_ts=1700000000&sign=SECRETSIG',
|
||||
true,
|
||||
'vk.com',
|
||||
);
|
||||
expect(lines[0]).toBe('launch: signed iframe: yes');
|
||||
expect(lines[1]).toBe('vk-platform: mobile_android');
|
||||
expect(lines[3]).toBe('missing: —');
|
||||
expect(lines[4]).toBe('referrer: vk.com');
|
||||
// The signature is auth material: its NAME may appear, its VALUE must never.
|
||||
const report = lines.join('\n');
|
||||
expect(report).toContain('sign');
|
||||
expect(report).not.toContain('SECRETSIG');
|
||||
});
|
||||
});
|
||||
|
||||
describe('vkLaunchDiag', () => {
|
||||
afterEach(() => vi.unstubAllGlobals());
|
||||
|
||||
it('builds a VK launch diagnostic with no Retry (VK signs the URL at load, nothing to wait for)', () => {
|
||||
vi.stubGlobal('location', { pathname: '/vk/', search: '?utm_source=catalog' });
|
||||
const d = vkLaunchDiag();
|
||||
expect(d.platform).toBe('vk');
|
||||
expect(d.retry).toBe(false);
|
||||
expect(d.report).toContain('launch: unsigned');
|
||||
});
|
||||
});
|
||||
|
||||
describe('appearanceForBg', () => {
|
||||
it('maps a dark background to the dark appearance (light status-bar icons)', () => {
|
||||
expect(appearanceForBg('#0f1115')).toBe('dark');
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// (VKWebAppGetUserInfo, since VK omits it from the signed launch params). Every helper is safe to
|
||||
// call outside VK.
|
||||
|
||||
import { clientEnvLines, queryFieldNames, type LaunchDiag } from './launchdiag';
|
||||
import { isExternalHttpUrl, type Haptic } from './telegram';
|
||||
|
||||
async function bridge() {
|
||||
@@ -108,6 +109,66 @@ export function vkPlatform(): string {
|
||||
return new URLSearchParams(location.search.replace(/^\?/, '')).get('vk_platform') ?? '';
|
||||
}
|
||||
|
||||
// --- Launch diagnostics (the /vk/ entry without a signed launch) ---
|
||||
|
||||
/** The launch parameters a valid VK Mini App launch is expected to carry in the URL; their absence —
|
||||
* notably `sign` — is the signal the launch-error screen reports. Only names are inspected. */
|
||||
const expectedVKParams = ['vk_app_id', 'vk_user_id', 'vk_ts', 'vk_platform', 'sign'];
|
||||
|
||||
/**
|
||||
* vkDiagLines formats the URL-derived lines of the VK launch diagnostic from a query string (no
|
||||
* leading '?') and two runtime flags. It reports only the launch-parameter NAMES present and the
|
||||
* expected ones missing — never a value, since `sign` is auth material — plus the non-secret
|
||||
* `vk_platform` and whether the URL carried a signature at all. Pure, so it is unit-tested.
|
||||
*/
|
||||
export function vkDiagLines(search: string, inIframe: boolean, referrerHost: string): string[] {
|
||||
const names = queryFieldNames(search);
|
||||
const platform = new URLSearchParams(search).get('vk_platform') ?? '';
|
||||
const missing = expectedVKParams.filter((p) => !names.includes(p));
|
||||
return [
|
||||
`launch: ${names.includes('sign') ? 'signed' : 'unsigned'} iframe: ${inIframe ? 'yes' : 'no'}`,
|
||||
`vk-platform: ${platform || '—'}`,
|
||||
`params: ${names.join(',') || '—'}`,
|
||||
`missing: ${missing.join(',') || '—'}`,
|
||||
`referrer: ${referrerHost || '—'}`,
|
||||
];
|
||||
}
|
||||
|
||||
/** vkInIframe reports whether the app runs inside an iframe (VK desktop frames the Mini App; a plain
|
||||
* browser tab is top-level). A cross-origin top frame denies property access, which itself means we
|
||||
* are framed, so that is caught as true. */
|
||||
function vkInIframe(): boolean {
|
||||
if (typeof window === 'undefined') return false;
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** vkReferrerHost returns the host of document.referrer (a real VK launch is referred from vk.com; a
|
||||
* direct type-in has none), or '' when there is no referrer or it does not parse. */
|
||||
function vkReferrerHost(): string {
|
||||
if (typeof document === 'undefined' || !document.referrer) return '';
|
||||
try {
|
||||
return new URL(document.referrer).host;
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* vkLaunchDiag captures the current VK launch state as a neutral LaunchDiag for the shared LaunchError
|
||||
* screen — a passive read of the URL launch parameters (names only), the iframe / referrer context and
|
||||
* the shared client environment. retry is false: VK delivers its signed parameters in the launch URL at
|
||||
* load, so — unlike Telegram's initData — they never arrive late, and a Retry could not recover them.
|
||||
*/
|
||||
export function vkLaunchDiag(): LaunchDiag {
|
||||
const search = typeof location === 'undefined' ? '' : location.search.replace(/^\?/, '');
|
||||
const report = [...vkDiagLines(search, vkInIframe(), vkReferrerHost()), ...clientEnvLines()].join('\n');
|
||||
return { platform: 'vk', report, retry: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* vkAndroidWebView reports whether the app runs inside the Android VK client's WebView
|
||||
* (vk_platform mobile_android / mobile_android_messenger) — the one environment whose WebView
|
||||
|
||||
Reference in New Issue
Block a user