fix(ui): load telegram-web-app.js dynamically with a timeout
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 55s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m18s

The Telegram SDK was a render-blocking <script src="telegram.org/..."> in the
shared index.html shell, so it ran on every entry (/telegram/, /app/, native).
On a network that blocks telegram.org — common where Telegram itself reaches
users only over a proxy — the script hangs forever, stranding the whole page,
including the launch-diagnostic screen meant to surface exactly this failure.
This is the likely root cause of the Android "won't open" reports (all launch
methods fail identically; iOS on a different network works).

Remove the head <script> and load the SDK dynamically (lib/telegram.ts
loadTelegramSDK) with a 10s timeout, only on a Telegram entry (the /telegram/
path or a tgWebApp launch fragment). The SPA — served from our own reachable
origin — boots first and controls the load: on a block/error it falls through
to the diagnostic screen (reporting sdk: no) instead of hanging, and Retry
re-attempts. /app/ and the native build no longer touch telegram.org.

Pin the SDK to the version the official page recommends (?62) for the newer
client features the app already uses (fullscreen, safe areas, swipe guard).

Tests: loadTelegramSDK unit tests (present / error / timeout); an e2e that
aborts the script fetch and asserts the diagnostic still renders.
This commit is contained in:
Ilia Denisov
2026-06-23 09:59:24 +02:00
parent 0c5d3808d7
commit ae5090b851
7 changed files with 129 additions and 12 deletions
+5 -5
View File
@@ -1,13 +1,13 @@
import { test as base } from '@playwright/test';
// All e2e specs run hermetically against the mock transport. Neutralise the real
// telegram-web-app.js (loaded from the CDN in index.html) so the suite never blocks
// on telegram.org — it is unreachable from the CI runner, and a render-blocking
// <script> to it would hang every page load. Specs that exercise the Telegram launch
// inject their own window.Telegram via addInitScript before navigating.
// telegram-web-app.js (the app loads it dynamically — see lib/telegram.ts loadTelegramSDK) so the
// suite never reaches telegram.org, which is unreachable from the CI runner. Specs that exercise
// the Telegram launch inject their own window.Telegram via addInitScript before navigating, so the
// dynamic load short-circuits on the already-present SDK.
export const test = base.extend({
page: async ({ page }, use) => {
await page.route('**/telegram-web-app.js', (route) =>
await page.route('**/telegram-web-app.js*', (route) =>
route.fulfill({ status: 200, contentType: 'application/javascript', body: '' }),
);
await use(page);
+12
View File
@@ -121,3 +121,15 @@ test('outside Telegram, the /telegram/ entry shows the launch diagnostic, not a
await expect(page).toHaveURL(/\/telegram\//);
await expect(page.getByRole('button', { name: /guest/i })).toHaveCount(0);
});
test('a blocked telegram-web-app.js does not hang the diagnostic screen', async ({ page }) => {
// Simulate a network where telegram.org is unreachable: the SDK fetch fails. Because the SPA
// loads the SDK dynamically with a timeout (not a render-blocking <script>), a failed/blocked
// fetch must not strand the page — the diagnostic screen still renders, reporting no SDK. (This
// route overrides the fixture's empty-body fulfill; the later registration wins.)
await page.route('**/telegram-web-app.js*', (route) => route.abort());
await page.goto('/telegram/');
await expect(page.getByRole('button', { name: 'Share' })).toBeVisible();
await expect(page.getByText('sdk: no')).toBeVisible();
});