From 8f31311112979beebeccf2952adcb8f2d4580fdd Mon Sep 17 00:00:00 2001 From: Fringg Date: Thu, 2 Jul 2026 16:12:18 +0300 Subject: [PATCH 01/12] fix(connection): keep the Happ add-subscription button working on Remnawave 2.8.0 Panel 2.8.0 removed the server-side happ-encrypt endpoint, so for users without a stored crypto link the backend leaves {{HAPP_CRYPT4_LINK}} unresolved and the subscriptionLink button silently disappeared (isValidDeepLink requires '://'). - BlockButtons: resolve button templates client-side at render (with username, like the panel's own subpage) instead of dropping the button; collapse double-prefixed happ://crypt4/happ://cryptN/... resolvedUrl from old backends - templateEngine: collapse a hardcoded happ://cryptN/ prefix before {{HAPP_CRYPT[34]_LINK}}; cache crypt-link generation (jsencrypt RSA-4096 is slow on weak devices and re-randomizes every call) - Connection: in happ_cryptolink mode force the crypt link only when the button fell back to the plain subscription link or kept unresolved templates - an explicit Subpage link (e.g. happ://add/...) now wins, so Subpage edits apply --- .../connection/InstallationGuide.tsx | 4 +++ .../connection/blocks/BlockButtons.tsx | 16 +++++++-- src/pages/Connection.tsx | 26 +++++++++++--- src/utils/templateEngine.ts | 34 +++++++++++++++++-- 4 files changed, 71 insertions(+), 9 deletions(-) diff --git a/src/components/connection/InstallationGuide.tsx b/src/components/connection/InstallationGuide.tsx index b5c6b9b..1f27888 100644 --- a/src/components/connection/InstallationGuide.tsx +++ b/src/components/connection/InstallationGuide.tsx @@ -48,6 +48,7 @@ interface Props { isTelegramWebApp: boolean; onGoBack: () => void; onOpenQR?: () => void; + username?: string; } export default function InstallationGuide({ @@ -56,6 +57,7 @@ export default function InstallationGuide({ isTelegramWebApp, onGoBack, onOpenQR, + username, }: Props) { const { t, i18n } = useTranslation(); const { isLight } = useTheme(); @@ -131,6 +133,7 @@ export default function InstallationGuide({ subscriptionUrl={appConfig.subscriptionUrl} hideLink={appConfig.hideLink} deepLink={selectedApp?.deepLink} + username={username} getLocalizedText={getLocalizedText} getBaseTranslation={getBaseTranslation} getSvgHtml={getSvgHtml} @@ -141,6 +144,7 @@ export default function InstallationGuide({ appConfig.subscriptionUrl, appConfig.hideLink, selectedApp?.deepLink, + username, isLight, getLocalizedText, getBaseTranslation, diff --git a/src/components/connection/blocks/BlockButtons.tsx b/src/components/connection/blocks/BlockButtons.tsx index d0eae14..35c42f8 100644 --- a/src/components/connection/blocks/BlockButtons.tsx +++ b/src/components/connection/blocks/BlockButtons.tsx @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'; import { CheckIcon, CopyIcon } from '@/components/icons'; import type { RemnawaveButtonClient, LocalizedText } from '@/types'; import { copyToClipboard } from '@/utils/clipboard'; +import { collapseDoubledCryptPrefix, hasTemplates, resolveTemplate } from '@/utils/templateEngine'; import { blockButtonClass } from './buttonStyles'; // eslint-disable-next-line no-script-url @@ -29,6 +30,7 @@ interface BlockButtonsProps { subscriptionUrl: string | null; hideLink?: boolean; deepLink?: string | null; + username?: string; getLocalizedText: (text: LocalizedText | undefined) => string; getBaseTranslation: (key: string, i18nKey: string) => string; getSvgHtml: (key: string | undefined) => string; @@ -42,6 +44,7 @@ export function BlockButtons({ subscriptionUrl, hideLink, deepLink, + username, getLocalizedText, getBaseTranslation, getSvgHtml, @@ -73,8 +76,17 @@ export function BlockButtons({ ) : null; if (btn.type === 'subscriptionLink') { - const url = btn.resolvedUrl || btn.url || btn.link || deepLink || subscriptionUrl; - if (!url || !isValidDeepLink(url)) return null; + const raw = btn.resolvedUrl || btn.url || btn.link || deepLink || subscriptionUrl; + // The backend resolves {{HAPP_CRYPT*_LINK}} only when a crypto link is + // stored in the DB; since Remnawave 2.8.0 removed the encrypt endpoint, + // new users can get the raw template here. Resolve it client-side (the + // panel's own subpage does the same) instead of dropping the button. + const resolved = + raw && subscriptionUrl && hasTemplates(raw) + ? resolveTemplate(raw, { subscriptionUrl, username }) + : raw; + const url = resolved ? collapseDoubledCryptPrefix(resolved) : resolved; + if (!url || hasTemplates(url) || !isValidDeepLink(url)) return null; return ( + + + +