From c80e1aff752d6d697cd6fedfbda6288c8e8e39cd Mon Sep 17 00:00:00 2001 From: Dxnil <62987903+D4nilKO@users.noreply.github.com> Date: Wed, 15 Apr 2026 03:29:54 +0300 Subject: [PATCH] fix(connection): happ cryptolink flow + ui fixes (#385) * fix(connection): respect happ_cryptolink mode - read connect_mode from connection-link endpoint - auto-redirect to happ cryptolink flow when mode is HAPP_CRYPTOLINK - keep installation guide behavior for other modes * docs(readme): fix source build step 2 - use compose service name for build/create commands - copy static files from created compose container - remove compose container via docker compose rm * fix(connection): handle mode and ws path - treat any non-guide connect mode as direct happ redirect - wait for connection-link response before rendering installation guide - build websocket URL from VITE_API_URL (supports /api and absolute URLs) * docs(readme): fix docker cp static path - copy /usr/share/nginx/html contents via html/. - prevent nested /srv/cabinet/html deployment and 404 on root * fix(connection): keep guide, use happ links - restore guide page behavior for /connection (no auto redirect) - use happ cryptolink URL in /connection/qr when happ crypt mode is active - replace subscription page connection URL with happ cryptolink from connection-link API * fix(connection): avoid wrong redirect flow - force happ scheme deeplink in HAPP_CRYPTOLINK mode for guide connect buttons - use cabinet redirect page only inside Telegram Mini App - open deeplink directly in regular browsers * fix(connection): enforce happ cryptolink - prioritize backend crypt deeplink fields in HAPP_CRYPTOLINK mode - fallback to local crypt4/crypt3 generation from subscription URL when backend returns plain happ://sub... - apply same resolution for guide open action, qr page source and subscription page link display/copy * fix(subscription): truncate long connect link - render connection URL in a single line with ellipsis on /subscriptions - preserve full URL in tooltip for easier manual copy - keep copy button behavior unchanged * fix(connection): build cryptolink from happ sub - allow cryptolink fallback generation from happ://sub... URLs in addition to http(s) - prevent plain happ://sub links from leaking into UI in HAPP_CRYPTOLINK mode --- README.md | 12 +++--- src/api/subscription.ts | 3 ++ src/pages/Connection.tsx | 65 +++++++++++++++++++++++----- src/pages/Subscription.tsx | 46 +++++++++++++++++--- src/providers/WebSocketProvider.tsx | 41 +++++++++++------- src/utils/connectionLink.ts | 67 +++++++++++++++++++++++++++++ 6 files changed, 198 insertions(+), 36 deletions(-) create mode 100644 src/utils/connectionLink.ts diff --git a/README.md b/README.md index f5cb751..cc89889 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,8 @@ docker pull ghcr.io/bedolaga-dev/bedolaga-cabinet:latest ```bash # Создать временный контейнер и скопировать статику docker create --name tmp_cabinet ghcr.io/bedolaga-dev/bedolaga-cabinet:latest -docker cp tmp_cabinet:/usr/share/nginx/html ./cabinet-dist +mkdir -p ./cabinet-dist +docker cp tmp_cabinet:/usr/share/nginx/html/. ./cabinet-dist/ docker rm tmp_cabinet ``` @@ -77,10 +78,11 @@ VITE_APP_LOGO=V Соберите и извлеките: ```bash -docker compose build -docker create --name tmp_cabinet cabinet_frontend -docker cp tmp_cabinet:/usr/share/nginx/html ./cabinet-dist -docker rm tmp_cabinet +docker compose build cabinet-frontend +docker compose create cabinet-frontend +mkdir -p ./cabinet-dist +docker cp cabinet_frontend:/usr/share/nginx/html/. ./cabinet-dist/ +docker compose rm -sf cabinet-frontend ``` ### Шаг 3. Размещение файлов на сервере diff --git a/src/api/subscription.ts b/src/api/subscription.ts index 529e934..2af14bf 100644 --- a/src/api/subscription.ts +++ b/src/api/subscription.ts @@ -449,6 +449,9 @@ export const subscriptionApi = { display_link: string | null; happ_redirect_link: string | null; happ_scheme_link: string | null; + happ_cryptolink?: string | null; + happ_crypto_link?: string | null; + happ_link?: string | null; connect_mode: string; hide_link: boolean; instructions: { diff --git a/src/pages/Connection.tsx b/src/pages/Connection.tsx index d22e5f4..a36c049 100644 --- a/src/pages/Connection.tsx +++ b/src/pages/Connection.tsx @@ -7,6 +7,7 @@ import { subscriptionApi } from '../api/subscription'; import { useTelegramSDK } from '../hooks/useTelegramSDK'; import { useHaptic } from '@/platform'; import { resolveTemplate, hasTemplates } from '../utils/templateEngine'; +import { isHappCryptolinkMode, resolveConnectionUrlForUi } from '../utils/connectionLink'; import { useAuthStore } from '../store/auth'; import type { AppConfig, RemnawavePlatformData } from '../types'; import InstallationGuide from '../components/connection/InstallationGuide'; @@ -32,21 +33,59 @@ export default function Connection() { queryKey: ['appConfig', subId], queryFn: () => subscriptionApi.getAppConfig(subId), }); + const { data: connectionLink, isLoading: isConnectionLinkLoading } = useQuery({ + queryKey: ['connectionLink', subId], + queryFn: () => subscriptionApi.getConnectionLink(subId), + retry: false, + staleTime: 0, + }); + + const qrConnectionUrl = useMemo( + () => + resolveConnectionUrlForUi({ + mode: connectionLink?.connect_mode, + happSchemeLink: connectionLink?.happ_scheme_link, + displayLink: connectionLink?.display_link, + subscriptionUrl: connectionLink?.subscription_url, + happCryptLink: connectionLink?.happ_cryptolink, + happCryptoLink: connectionLink?.happ_crypto_link, + happLink: connectionLink?.happ_link, + fallbackUrl: appConfig?.subscriptionUrl, + }), + [ + appConfig?.subscriptionUrl, + connectionLink?.connect_mode, + connectionLink?.display_link, + connectionLink?.happ_cryptolink, + connectionLink?.happ_crypto_link, + connectionLink?.happ_link, + connectionLink?.happ_scheme_link, + connectionLink?.subscription_url, + ], + ); const handleGoBack = useCallback(() => { navigate(-1); }, [navigate]); const handleOpenQR = useCallback(() => { + if (!qrConnectionUrl) return; navigate('/connection/qr', { replace: !isTelegramWebApp, state: { - url: appConfig?.subscriptionUrl, - hideLink: appConfig?.hideLink ?? false, + url: qrConnectionUrl, + hideLink: connectionLink?.hide_link ?? appConfig?.hideLink ?? false, subscriptionId: subId, }, }); - }, [navigate, appConfig?.subscriptionUrl, appConfig?.hideLink, isTelegramWebApp, subId]); + }, [ + navigate, + qrConnectionUrl, + connectionLink?.hide_link, + appConfig?.hideLink, + isTelegramWebApp, + subId, + ]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { @@ -73,24 +112,30 @@ export default function Connection() { const openDeepLink = useCallback( (deepLink: string) => { let resolved = deepLink; - if (hasTemplates(resolved)) { + if (isHappCryptolinkMode(connectionLink?.connect_mode) && qrConnectionUrl) { + // In HAPP cryptolink mode always open the resolved happ://crypt... URL. + resolved = qrConnectionUrl; + } else if (hasTemplates(resolved)) { resolved = resolveUrl(resolved); } - - const finalUrl = `${window.location.origin}/miniapp/redirect.html?url=${encodeURIComponent(resolved)}&lang=${i18n.language || 'en'}`; + const isHttpUrl = /^https?:\/\//i.test(resolved); + const finalUrlForTelegram = isHttpUrl + ? resolved + : `${window.location.origin}/miniapp/redirect.html?url=${encodeURIComponent(resolved)}&lang=${i18n.language || 'en'}`; if (isTelegramWebApp) { try { - sdkOpenLink(finalUrl, { tryInstantView: false }); + sdkOpenLink(finalUrlForTelegram, { tryInstantView: false }); return; } catch { // SDK not available, fallback } } - window.location.href = finalUrl; + // In regular browsers open deeplink directly (without intermediate redirect page). + window.location.href = resolved; }, - [isTelegramWebApp, i18n.language, resolveUrl], + [isTelegramWebApp, i18n.language, resolveUrl, connectionLink?.connect_mode, qrConnectionUrl], ); // Check if any platform has configured apps @@ -101,7 +146,7 @@ export default function Connection() { ); }, [appConfig?.platforms]); - if (isLoading) { + if (isLoading || isConnectionLinkLoading) { return (
- {subscription.subscription_url}
+ {displayedConnectionUrl}