feat(export): server-rendered artifacts behind one signed download URL #160

Merged
developer merged 7 commits from feature/export-server-render into development 2026-07-02 21:58:07 +00:00
Showing only changes of commit a9376c20a2 - Show all commits
+9 -9
View File
@@ -1,11 +1,11 @@
import { Buffer } from 'node:buffer';
import { expect, test, type Page } from './fixtures'; import { expect, test, type Page } from './fixtures';
// The finished-game export: the history header's 📤 button opens the app's own format // The finished-game export: one signed relative URL (game.export_url) resolved against
// chooser (never Telegram's native popup — its callback carries no user activation), // the app's own origin, delivered by the most native affordance per platform — Telegram's
// and BOTH formats travel the same unified route on every platform: the client mints a // showPopup chooser + downloadFile dialog (all bridge calls, activation-safe), VK's
// signed relative URL (game.export_url), resolves it against its own origin and hands // native viewer/download, the OS share sheet with the fetched file on a mobile browser,
// it to the platform's native download — Telegram downloadFile / VKWebAppDownloadFile — // a plain anchor download on desktop. g3 ("vs Rick") is the seeded finished game.
// or a plain anchor download in a browser. g3 ("vs Rick") is the seeded finished game.
// The mock gateway returns a shape-faithful /dl/... path; the tests intercept that // The mock gateway returns a shape-faithful /dl/... path; the tests intercept that
// route and serve bytes, so the download itself is exercised end to end. // route and serve bytes, so the download itself is exercised end to end.
@@ -13,9 +13,9 @@ import { expect, test, type Page } from './fixtures';
const PNG_HEX = const PNG_HEX =
'89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489' + '89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489' +
'0000000a49444154789c6360000000020001e221bc330000000049454e44ae426082'; '0000000a49444154789c6360000000020001e221bc330000000049454e44ae426082';
const PNG_BYTES = Uint8Array.from({ length: PNG_HEX.length / 2 }, (_, i) => // Buffer, not Uint8Array: route.fulfill silently hangs a FETCH interception on a
parseInt(PNG_HEX.slice(i * 2, i * 2 + 2), 16), // Uint8Array body (a navigation download tolerates it) — the share test deadlocked.
); const PNG_BYTES = Buffer.from(PNG_HEX, 'hex');
async function routeDl(page: Page): Promise<void> { async function routeDl(page: Page): Promise<void> {
await page.route('**/dl/**', (route) => { await page.route('**/dl/**', (route) => {