mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
fix(connection): stop ERR_UNKNOWN_URL_SCHEME when opening app deep links on mobile
Telegram bug #654272: opening the connection app link (immediate-open / connect button) showed a full-page net::ERR_UNKNOWN_URL_SCHEME on Android and silently failed on iOS, while working on desktop. Cause: a programmatic top-level navigation to a custom scheme (happ://, v2rayng://, …) via window.location.href is rendered as a full-page error inside in-app WebViews (Telegram/Yandex) on Android and does nothing on iOS — also wiping the fallback UI. Add openAppScheme(): http(s) navigate normally; custom schemes launch via a hidden, contained iframe so a failed launch never replaces the page — the app opens if installed, otherwise the manual 'Open app' link stays usable (a user tap is the reliable trigger). Applied in DeepLinkRedirect.tsx, Connection.tsx and the static public/miniapp/redirect.html (which now also surfaces the manual button immediately instead of after 2s).
This commit is contained in:
@@ -148,14 +148,27 @@
|
|||||||
const manualBtn = document.getElementById('manualBtn');
|
const manualBtn = document.getElementById('manualBtn');
|
||||||
manualBtn.href = url;
|
manualBtn.href = url;
|
||||||
|
|
||||||
// Try to open the URL scheme directly
|
// Attempt to launch the app WITHOUT a top-level navigation. A direct
|
||||||
// This page is opened in external browser, so custom schemes work
|
// `location.href = scheme` paints a full-page net::ERR_UNKNOWN_URL_SCHEME
|
||||||
window.location.href = url;
|
// inside Android in-app browsers (Telegram/Yandex/…) and silently fails on
|
||||||
|
// iOS — hiding the manual button (Telegram bug #654272). A hidden iframe is
|
||||||
|
// contained: the app opens if installed, otherwise the failure stays inside
|
||||||
|
// the (invisible) frame and the button below stays usable.
|
||||||
|
try {
|
||||||
|
var frame = document.createElement('iframe');
|
||||||
|
frame.style.display = 'none';
|
||||||
|
frame.src = url;
|
||||||
|
document.body.appendChild(frame);
|
||||||
|
setTimeout(function() {
|
||||||
|
try { frame.parentNode.removeChild(frame); } catch (e) {}
|
||||||
|
}, 2000);
|
||||||
|
} catch (e) {
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
|
||||||
// Show manual button after a delay in case redirect didn't work
|
// Programmatic opening is unreliable in in-app browsers, so surface the
|
||||||
setTimeout(function() {
|
// manual "Open app" button immediately — a user tap is the reliable trigger.
|
||||||
document.getElementById('errorBlock').classList.add('show');
|
document.getElementById('errorBlock').classList.add('show');
|
||||||
}, 2000);
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useTelegramSDK } from '../hooks/useTelegramSDK';
|
|||||||
import { useHaptic } from '@/platform';
|
import { useHaptic } from '@/platform';
|
||||||
import { SettingsIcon } from '@/components/icons';
|
import { SettingsIcon } from '@/components/icons';
|
||||||
import { resolveTemplate, hasTemplates } from '../utils/templateEngine';
|
import { resolveTemplate, hasTemplates } from '../utils/templateEngine';
|
||||||
|
import { openAppScheme } from '../utils/openAppScheme';
|
||||||
import { isHappCryptolinkMode, resolveConnectionUrlForUi } from '../utils/connectionLink';
|
import { isHappCryptolinkMode, resolveConnectionUrlForUi } from '../utils/connectionLink';
|
||||||
import { useAuthStore } from '../store/auth';
|
import { useAuthStore } from '../store/auth';
|
||||||
import type { AppConfig, RemnawavePlatformData } from '../types';
|
import type { AppConfig, RemnawavePlatformData } from '../types';
|
||||||
@@ -133,8 +134,11 @@ export default function Connection() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In regular browsers open deeplink directly (without intermediate redirect page).
|
// In regular browsers open the deeplink directly. openAppScheme uses a contained
|
||||||
window.location.href = resolved;
|
// iframe for custom schemes so an unresolved scheme doesn't paint a full-page
|
||||||
|
// net::ERR_UNKNOWN_URL_SCHEME (Android) / silently fail (iOS); http(s) links
|
||||||
|
// still navigate normally. (Telegram bug #654272.)
|
||||||
|
openAppScheme(resolved);
|
||||||
},
|
},
|
||||||
[isTelegramWebApp, i18n.language, resolveUrl, connectionLink?.connect_mode, qrConnectionUrl],
|
[isTelegramWebApp, i18n.language, resolveUrl, connectionLink?.connect_mode, qrConnectionUrl],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { brandingApi } from '../api/branding';
|
import { brandingApi } from '../api/branding';
|
||||||
import { copyToClipboard } from '../utils/clipboard';
|
import { copyToClipboard } from '../utils/clipboard';
|
||||||
|
import { openAppScheme } from '../utils/openAppScheme';
|
||||||
import {
|
import {
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
@@ -97,10 +98,12 @@ export default function DeepLinkRedirect() {
|
|||||||
const appName = appInfo?.name || appParam || 'VPN';
|
const appName = appInfo?.name || appParam || 'VPN';
|
||||||
const appIcon = appInfo?.icon || appName[0]?.toUpperCase() || 'V';
|
const appIcon = appInfo?.icon || appName[0]?.toUpperCase() || 'V';
|
||||||
|
|
||||||
// Open deep link - same as miniapp, just window.location.href
|
// Open deep link via a contained iframe attempt so a custom scheme the in-app
|
||||||
|
// WebView can't resolve doesn't replace this page with net::ERR_UNKNOWN_URL_SCHEME
|
||||||
|
// (Android) / silently fail (iOS) and wipe the fallback UI. See openAppScheme.
|
||||||
const openDeepLink = useCallback(() => {
|
const openDeepLink = useCallback(() => {
|
||||||
if (!deepLink || !isValidDeepLink(deepLink)) return;
|
if (!deepLink || !isValidDeepLink(deepLink)) return;
|
||||||
window.location.href = deepLink;
|
openAppScheme(deepLink);
|
||||||
}, [deepLink]);
|
}, [deepLink]);
|
||||||
|
|
||||||
// Countdown timer effect
|
// Countdown timer effect
|
||||||
|
|||||||
39
src/utils/openAppScheme.ts
Normal file
39
src/utils/openAppScheme.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Launch a custom-scheme app deep link (happ://, v2rayng://, vless://, …) without
|
||||||
|
* crashing the page inside in-app browsers.
|
||||||
|
*
|
||||||
|
* Why not `window.location.href = scheme`: a programmatic top-level navigation to a
|
||||||
|
* scheme the WebView can't resolve renders a full-page error — on Android in-app
|
||||||
|
* browsers (Telegram/Yandex/…) `net::ERR_UNKNOWN_URL_SCHEME`, on iOS it silently does
|
||||||
|
* nothing — which destroys the fallback UI (Telegram bug #654272). A hidden <iframe>
|
||||||
|
* navigation is contained: if the app is installed the OS intercepts it; if not, the
|
||||||
|
* failure stays inside the (invisible) frame and our page — with its manual "Open app"
|
||||||
|
* link — survives so the user can tap it (a real user gesture is the reliable trigger).
|
||||||
|
*
|
||||||
|
* http(s) links are normal navigations and are passed straight to location.href.
|
||||||
|
*/
|
||||||
|
export function openAppScheme(url: string): void {
|
||||||
|
const isHttp = /^https?:\/\//i.test(url);
|
||||||
|
if (isHttp) {
|
||||||
|
window.location.href = url;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.style.display = 'none';
|
||||||
|
iframe.src = url;
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
window.setTimeout(() => {
|
||||||
|
try {
|
||||||
|
iframe.remove();
|
||||||
|
} catch {
|
||||||
|
/* already detached */
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
} catch {
|
||||||
|
// iframe creation blocked (very old/locked-down WebView) — fall back to direct
|
||||||
|
// navigation. Worst case this shows the same error the iframe avoided, never worse.
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user