chore(ui): on-screen GCG export diagnostic (Android in-app WebView)
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 58s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m18s
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 58s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m18s
TEMPORARY diagnostic to find why GCG export silently does nothing on
Android Telegram/VK in-app WebViews, where there is no developer console
to read. Tapping Export now probes the delivery path — navigator.share /
canShare presence, canShare({files}), the share-vs-download decision —
performs the real attempt and captures its outcome, then renders it all in
an on-screen overlay (with a Share control to send the report back).
No fix yet by design (owner chose diagnostic-first): once the on-device
readings come back, this is replaced by the targeted Android delivery fix.
This commit is contained in:
+64
-2
@@ -20,7 +20,9 @@
|
||||
import { variantNameKey, usesStarBlank, BLANK_STAR } from '../lib/variants';
|
||||
import { alphabetLetters, hasAlphabet } from '../lib/alphabet';
|
||||
import { hintsLeft } from '../lib/hints';
|
||||
import { shareOrDownloadGcg } from '../lib/share';
|
||||
import { gcgDeliveryProbe, shareText } from '../lib/share';
|
||||
import { clientChannel } from '../lib/channel';
|
||||
import { insideVK } from '../lib/vk';
|
||||
import { getCachedGame, setCachedGame, setCachedDraft, type CachedGame } from '../lib/gamecache';
|
||||
import { patchLobbyGame } from '../lib/lobbycache';
|
||||
import { applyGameOver, applyMoveDelta, applyOpponentJoined, type DeltaResult } from '../lib/gamedelta';
|
||||
@@ -835,14 +837,31 @@
|
||||
return view.game.seats.some((s) => s.isWinner) ? t('game.lost') : t('game.tied');
|
||||
}
|
||||
|
||||
// TEMPORARY: on-screen GCG-export diagnostic for the Android in-app WebView bug. Telegram and VK on
|
||||
// Android expose no developer console, so tapping Export probes the delivery path, performs the real
|
||||
// attempt, and renders the outcome on screen (the overlay below) to be read or shared. Revert to the
|
||||
// plain shareOrDownloadGcg call once the Android delivery is fixed.
|
||||
let gcgDiag = $state<string | null>(null);
|
||||
|
||||
async function exportGcg() {
|
||||
try {
|
||||
await shareOrDownloadGcg(await gateway.exportGcg(id));
|
||||
const gcg = await gateway.exportGcg(id);
|
||||
const header = [
|
||||
`channel: ${clientChannel()} insideVK: ${insideVK()}`,
|
||||
`ua: ${navigator.userAgent}`,
|
||||
'',
|
||||
];
|
||||
gcgDiag = [...header, ...(await gcgDeliveryProbe(gcg))].join('\n');
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function shareGcgDiag(e: MouseEvent): Promise<void> {
|
||||
e.stopPropagation(); // a tap on Share shares; it must not also dismiss the overlay
|
||||
if (gcgDiag) await shareText(gcgDiag, 'GCG export diagnostic');
|
||||
}
|
||||
|
||||
// --- move history: open by tapping the score bar, close by tapping or swiping up the board ---
|
||||
// The boardwrap surface drives two gestures, selected by `historyOpen`:
|
||||
// - open: the slid board is inert (CSS pointer-events), so the whole board reads as a
|
||||
@@ -1339,7 +1358,50 @@
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
{#if gcgDiag}
|
||||
<!-- TEMPORARY on-screen GCG-export diagnostic for the Android in-app WebView bug (Telegram/VK
|
||||
expose no devtools). Tap anywhere to dismiss; Share sends the report. Revert with the fix. -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="gcgdiag" onclick={() => (gcgDiag = null)}>
|
||||
<button class="gcgdiag-share" onclick={shareGcgDiag}>Share</button>
|
||||
<pre class="gcgdiag-body">{gcgDiag}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
/* TEMPORARY GCG-export diagnostic overlay — mirrors DebugPanel; remove with the diagnostic. */
|
||||
.gcgdiag {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10000;
|
||||
padding: calc(var(--tg-safe-top, 0px) + 56px) 12px 16px;
|
||||
background: rgba(0, 0, 0, 0.82);
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.gcgdiag-share {
|
||||
flex: 0 0 auto;
|
||||
padding: 7px 16px;
|
||||
border: 1px solid var(--accent);
|
||||
background: var(--accent);
|
||||
color: var(--accent-text);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.gcgdiag-body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.45;
|
||||
color: #d6e6ff;
|
||||
}
|
||||
.scoreboard {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@@ -52,6 +52,57 @@ function downloadFile(content: string, filename: string): void {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/** diagErr renders an unknown thrown value as 'Name: message', else its string form. */
|
||||
function diagErr(e: unknown): string {
|
||||
return e instanceof Error ? `${e.name}: ${e.message}` : String(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* gcgDeliveryProbe is a TEMPORARY on-screen diagnostic for the GCG export on Android in-app WebViews
|
||||
* (Telegram / VK), where the export silently does nothing and those clients expose no developer
|
||||
* console. It reports the Web-Share / blob-download decision and the ACTUAL delivery outcome as
|
||||
* human-readable lines — it performs the same attempt shareOrDownloadGcg would — so the caller can
|
||||
* show the result on screen. Remove together with this diagnostic once the Android delivery is fixed.
|
||||
*/
|
||||
export async function gcgDeliveryProbe(gcg: GcgExport): Promise<string[]> {
|
||||
const file = new File([gcg.content], gcg.filename, { type: 'application/x-gcg' });
|
||||
const nav = typeof navigator !== 'undefined' ? navigator : undefined;
|
||||
const hasShare = !!nav && typeof nav.share === 'function';
|
||||
const hasCanShare = !!nav && typeof nav.canShare === 'function';
|
||||
let canShareFiles = 'n/a (no canShare)';
|
||||
if (hasCanShare) {
|
||||
try {
|
||||
canShareFiles = String(nav!.canShare!({ files: [file] }));
|
||||
} catch (e) {
|
||||
canShareFiles = `threw — ${diagErr(e)}`;
|
||||
}
|
||||
}
|
||||
const decision = pickGcgDelivery(nav, file);
|
||||
const lines = [
|
||||
`file: ${gcg.filename} (${gcg.content.length} bytes)`,
|
||||
`navigator.share: ${hasShare}`,
|
||||
`navigator.canShare: ${hasCanShare}`,
|
||||
`canShare({files}): ${canShareFiles}`,
|
||||
`decision: ${decision}`,
|
||||
];
|
||||
if (decision === 'share' && nav) {
|
||||
try {
|
||||
await nav.share({ files: [file], title: gcg.filename });
|
||||
lines.push('share(): resolved');
|
||||
} catch (e) {
|
||||
lines.push(`share(): rejected — ${diagErr(e)}`);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
downloadFile(gcg.content, gcg.filename);
|
||||
lines.push('download(): anchor click issued, no throw');
|
||||
} catch (e) {
|
||||
lines.push(`download(): threw — ${diagErr(e)}`);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
type TextShareNav = Pick<Navigator, 'share'> & { canShare?: Navigator['canShare'] };
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user