mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
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
This commit is contained in:
12
README.md
12
README.md
@@ -53,7 +53,8 @@ docker pull ghcr.io/bedolaga-dev/bedolaga-cabinet:latest
|
|||||||
```bash
|
```bash
|
||||||
# Создать временный контейнер и скопировать статику
|
# Создать временный контейнер и скопировать статику
|
||||||
docker create --name tmp_cabinet ghcr.io/bedolaga-dev/bedolaga-cabinet:latest
|
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
|
docker rm tmp_cabinet
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -77,10 +78,11 @@ VITE_APP_LOGO=V
|
|||||||
Соберите и извлеките:
|
Соберите и извлеките:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose build
|
docker compose build cabinet-frontend
|
||||||
docker create --name tmp_cabinet cabinet_frontend
|
docker compose create cabinet-frontend
|
||||||
docker cp tmp_cabinet:/usr/share/nginx/html ./cabinet-dist
|
mkdir -p ./cabinet-dist
|
||||||
docker rm tmp_cabinet
|
docker cp cabinet_frontend:/usr/share/nginx/html/. ./cabinet-dist/
|
||||||
|
docker compose rm -sf cabinet-frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
### Шаг 3. Размещение файлов на сервере
|
### Шаг 3. Размещение файлов на сервере
|
||||||
|
|||||||
@@ -449,6 +449,9 @@ export const subscriptionApi = {
|
|||||||
display_link: string | null;
|
display_link: string | null;
|
||||||
happ_redirect_link: string | null;
|
happ_redirect_link: string | null;
|
||||||
happ_scheme_link: string | null;
|
happ_scheme_link: string | null;
|
||||||
|
happ_cryptolink?: string | null;
|
||||||
|
happ_crypto_link?: string | null;
|
||||||
|
happ_link?: string | null;
|
||||||
connect_mode: string;
|
connect_mode: string;
|
||||||
hide_link: boolean;
|
hide_link: boolean;
|
||||||
instructions: {
|
instructions: {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { subscriptionApi } from '../api/subscription';
|
|||||||
import { useTelegramSDK } from '../hooks/useTelegramSDK';
|
import { useTelegramSDK } from '../hooks/useTelegramSDK';
|
||||||
import { useHaptic } from '@/platform';
|
import { useHaptic } from '@/platform';
|
||||||
import { resolveTemplate, hasTemplates } from '../utils/templateEngine';
|
import { resolveTemplate, hasTemplates } from '../utils/templateEngine';
|
||||||
|
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';
|
||||||
import InstallationGuide from '../components/connection/InstallationGuide';
|
import InstallationGuide from '../components/connection/InstallationGuide';
|
||||||
@@ -32,21 +33,59 @@ export default function Connection() {
|
|||||||
queryKey: ['appConfig', subId],
|
queryKey: ['appConfig', subId],
|
||||||
queryFn: () => subscriptionApi.getAppConfig(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(() => {
|
const handleGoBack = useCallback(() => {
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
}, [navigate]);
|
}, [navigate]);
|
||||||
|
|
||||||
const handleOpenQR = useCallback(() => {
|
const handleOpenQR = useCallback(() => {
|
||||||
|
if (!qrConnectionUrl) return;
|
||||||
navigate('/connection/qr', {
|
navigate('/connection/qr', {
|
||||||
replace: !isTelegramWebApp,
|
replace: !isTelegramWebApp,
|
||||||
state: {
|
state: {
|
||||||
url: appConfig?.subscriptionUrl,
|
url: qrConnectionUrl,
|
||||||
hideLink: appConfig?.hideLink ?? false,
|
hideLink: connectionLink?.hide_link ?? appConfig?.hideLink ?? false,
|
||||||
subscriptionId: subId,
|
subscriptionId: subId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [navigate, appConfig?.subscriptionUrl, appConfig?.hideLink, isTelegramWebApp, subId]);
|
}, [
|
||||||
|
navigate,
|
||||||
|
qrConnectionUrl,
|
||||||
|
connectionLink?.hide_link,
|
||||||
|
appConfig?.hideLink,
|
||||||
|
isTelegramWebApp,
|
||||||
|
subId,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
@@ -73,24 +112,30 @@ export default function Connection() {
|
|||||||
const openDeepLink = useCallback(
|
const openDeepLink = useCallback(
|
||||||
(deepLink: string) => {
|
(deepLink: string) => {
|
||||||
let resolved = deepLink;
|
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);
|
resolved = resolveUrl(resolved);
|
||||||
}
|
}
|
||||||
|
const isHttpUrl = /^https?:\/\//i.test(resolved);
|
||||||
const finalUrl = `${window.location.origin}/miniapp/redirect.html?url=${encodeURIComponent(resolved)}&lang=${i18n.language || 'en'}`;
|
const finalUrlForTelegram = isHttpUrl
|
||||||
|
? resolved
|
||||||
|
: `${window.location.origin}/miniapp/redirect.html?url=${encodeURIComponent(resolved)}&lang=${i18n.language || 'en'}`;
|
||||||
|
|
||||||
if (isTelegramWebApp) {
|
if (isTelegramWebApp) {
|
||||||
try {
|
try {
|
||||||
sdkOpenLink(finalUrl, { tryInstantView: false });
|
sdkOpenLink(finalUrlForTelegram, { tryInstantView: false });
|
||||||
return;
|
return;
|
||||||
} catch {
|
} catch {
|
||||||
// SDK not available, fallback
|
// 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
|
// Check if any platform has configured apps
|
||||||
@@ -101,7 +146,7 @@ export default function Connection() {
|
|||||||
);
|
);
|
||||||
}, [appConfig?.platforms]);
|
}, [appConfig?.platforms]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading || isConnectionLinkLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 items-center justify-center py-20">
|
<div className="flex flex-1 items-center justify-center py-20">
|
||||||
<div className="h-10 w-10 animate-spin rounded-full border-[3px] border-accent-500/30 border-t-accent-500" />
|
<div className="h-10 w-10 animate-spin rounded-full border-[3px] border-accent-500/30 border-t-accent-500" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useRef, useCallback, memo } from 'react';
|
import { useState, useEffect, useRef, useCallback, useMemo, memo } from 'react';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Navigate, useNavigate, useParams } from 'react-router';
|
import { Navigate, useNavigate, useParams } from 'react-router';
|
||||||
@@ -18,6 +18,7 @@ import { useCloseOnSuccessNotification } from '../store/successNotification';
|
|||||||
import PurchaseCTAButton from '../components/subscription/PurchaseCTAButton';
|
import PurchaseCTAButton from '../components/subscription/PurchaseCTAButton';
|
||||||
import { CopyIcon, CheckIcon } from '../components/icons';
|
import { CopyIcon, CheckIcon } from '../components/icons';
|
||||||
import { useHaptic } from '../platform';
|
import { useHaptic } from '../platform';
|
||||||
|
import { resolveConnectionUrlForUi } from '../utils/connectionLink';
|
||||||
import {
|
import {
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
getInsufficientBalanceError,
|
getInsufficientBalanceError,
|
||||||
@@ -221,9 +222,41 @@ export default function Subscription() {
|
|||||||
staleTime: 0,
|
staleTime: 0,
|
||||||
refetchOnMount: 'always',
|
refetchOnMount: 'always',
|
||||||
});
|
});
|
||||||
|
const { data: connectionLink, isLoading: isConnectionLinkLoading } = useQuery({
|
||||||
|
queryKey: ['connection-link', subscriptionId],
|
||||||
|
queryFn: () => subscriptionApi.getConnectionLink(subscriptionId),
|
||||||
|
retry: false,
|
||||||
|
staleTime: 0,
|
||||||
|
});
|
||||||
|
|
||||||
// Extract subscription from response (null if no subscription)
|
// Extract subscription from response (null if no subscription)
|
||||||
const subscription = subscriptionResponse?.subscription ?? null;
|
const subscription = subscriptionResponse?.subscription ?? null;
|
||||||
|
const displayedConnectionUrl = 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: isConnectionLinkLoading ? null : (subscription?.subscription_url ?? null),
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
connectionLink?.connect_mode,
|
||||||
|
connectionLink?.display_link,
|
||||||
|
connectionLink?.happ_cryptolink,
|
||||||
|
connectionLink?.happ_crypto_link,
|
||||||
|
connectionLink?.happ_link,
|
||||||
|
connectionLink?.happ_scheme_link,
|
||||||
|
connectionLink?.subscription_url,
|
||||||
|
isConnectionLinkLoading,
|
||||||
|
subscription?.subscription_url,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
const shouldHideConnectionLink =
|
||||||
|
subscription?.hide_subscription_link || connectionLink?.hide_link;
|
||||||
|
|
||||||
// Traffic zone (theme-aware) — called unconditionally at top level
|
// Traffic zone (theme-aware) — called unconditionally at top level
|
||||||
const usedPercent = trafficData?.traffic_used_percent ?? subscription?.traffic_used_percent ?? 0;
|
const usedPercent = trafficData?.traffic_used_percent ?? subscription?.traffic_used_percent ?? 0;
|
||||||
@@ -453,8 +486,8 @@ export default function Subscription() {
|
|||||||
}, [subscription, refreshTrafficMutation, subscriptionId]);
|
}, [subscription, refreshTrafficMutation, subscriptionId]);
|
||||||
|
|
||||||
const copyUrl = () => {
|
const copyUrl = () => {
|
||||||
if (subscription?.subscription_url) {
|
if (displayedConnectionUrl) {
|
||||||
navigator.clipboard.writeText(subscription.subscription_url);
|
navigator.clipboard.writeText(displayedConnectionUrl);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
}
|
}
|
||||||
@@ -890,16 +923,17 @@ export default function Subscription() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ─── Subscription URL ─── */}
|
{/* ─── Subscription URL ─── */}
|
||||||
{subscription.subscription_url && !subscription.hide_subscription_link && (
|
{displayedConnectionUrl && !shouldHideConnectionLink && (
|
||||||
<div className="mb-5 flex gap-2">
|
<div className="mb-5 flex gap-2">
|
||||||
<code
|
<code
|
||||||
className="scrollbar-hide flex-1 overflow-x-auto break-all rounded-[10px] px-3 py-2 font-mono text-[11px] text-dark-50/30"
|
className="block min-w-0 flex-1 truncate whitespace-nowrap rounded-[10px] px-3 py-2 font-mono text-[11px] text-dark-50/30"
|
||||||
style={{
|
style={{
|
||||||
background: g.codeBg,
|
background: g.codeBg,
|
||||||
border: `1px solid ${g.codeBorder}`,
|
border: `1px solid ${g.codeBorder}`,
|
||||||
}}
|
}}
|
||||||
|
title={displayedConnectionUrl}
|
||||||
>
|
>
|
||||||
{subscription.subscription_url}
|
{displayedConnectionUrl}
|
||||||
</code>
|
</code>
|
||||||
<button
|
<button
|
||||||
onClick={copyUrl}
|
onClick={copyUrl}
|
||||||
|
|||||||
@@ -8,6 +8,31 @@ export type { WSMessage } from './WebSocketContext';
|
|||||||
|
|
||||||
const isDev = import.meta.env.DEV;
|
const isDev = import.meta.env.DEV;
|
||||||
|
|
||||||
|
function buildWebSocketUrl(accessToken: string): string {
|
||||||
|
const apiUrl = String(import.meta.env.VITE_API_URL || '/api').trim();
|
||||||
|
const windowWsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
|
||||||
|
const withToken = (base: string) => `${base}?token=${encodeURIComponent(accessToken)}`;
|
||||||
|
|
||||||
|
if (apiUrl.startsWith('http://') || apiUrl.startsWith('https://')) {
|
||||||
|
try {
|
||||||
|
const api = new URL(apiUrl);
|
||||||
|
const wsProtocol = api.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
const basePath = api.pathname.replace(/\/+$/, '');
|
||||||
|
const wsPath = basePath.endsWith('/cabinet') ? `${basePath}/ws` : `${basePath}/cabinet/ws`;
|
||||||
|
return withToken(`${wsProtocol}//${api.host}${wsPath}`);
|
||||||
|
} catch {
|
||||||
|
// fall through to relative-path handling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedBasePath = `/${apiUrl}`.replace(/\/{2,}/g, '/').replace(/\/+$/, '');
|
||||||
|
const wsPath = normalizedBasePath.endsWith('/cabinet')
|
||||||
|
? `${normalizedBasePath}/ws`
|
||||||
|
: `${normalizedBasePath}/cabinet/ws`;
|
||||||
|
return withToken(`${windowWsProtocol}//${window.location.host}${wsPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
export function WebSocketProvider({ children }: { children: React.ReactNode }) {
|
export function WebSocketProvider({ children }: { children: React.ReactNode }) {
|
||||||
const accessToken = useAuthStore((state) => state.accessToken);
|
const accessToken = useAuthStore((state) => state.accessToken);
|
||||||
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||||
@@ -48,21 +73,7 @@ export function WebSocketProvider({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
||||||
// Build WebSocket URL
|
const wsUrl = buildWebSocketUrl(accessToken);
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
||||||
let host = window.location.host;
|
|
||||||
|
|
||||||
// Handle VITE_API_URL - can be absolute URL or relative path
|
|
||||||
const apiUrl = import.meta.env.VITE_API_URL;
|
|
||||||
if (apiUrl && (apiUrl.startsWith('http://') || apiUrl.startsWith('https://'))) {
|
|
||||||
try {
|
|
||||||
host = new URL(apiUrl).host;
|
|
||||||
} catch {
|
|
||||||
// If URL parsing fails, use window.location.host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const wsUrl = `${protocol}//${host}/cabinet/ws?token=${accessToken}`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ws = new WebSocket(wsUrl);
|
const ws = new WebSocket(wsUrl);
|
||||||
|
|||||||
67
src/utils/connectionLink.ts
Normal file
67
src/utils/connectionLink.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { createHappCryptoLink } from '@kastov/cryptohapp';
|
||||||
|
|
||||||
|
export function isHappCryptolinkMode(mode: string | null | undefined): boolean {
|
||||||
|
const normalized = String(mode ?? '').toUpperCase();
|
||||||
|
if (!normalized) return false;
|
||||||
|
return normalized.includes('HAPP') && normalized.includes('CRYPT');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHttpUrl(url: string | null | undefined): url is string {
|
||||||
|
return typeof url === 'string' && /^https?:\/\//i.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHappSubscriptionLink(url: string | null | undefined): url is string {
|
||||||
|
return typeof url === 'string' && /^happ:\/\/sub/i.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCryptSourceUrl(url: string | null | undefined): url is string {
|
||||||
|
return isHttpUrl(url) || isHappSubscriptionLink(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHappCryptDeepLink(url: string | null | undefined): url is string {
|
||||||
|
return typeof url === 'string' && /^happ:\/\/crypt/i.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResolveConnectionUrlInput {
|
||||||
|
mode?: string | null;
|
||||||
|
subscriptionUrl?: string | null;
|
||||||
|
displayLink?: string | null;
|
||||||
|
happSchemeLink?: string | null;
|
||||||
|
happCryptLink?: string | null;
|
||||||
|
happCryptoLink?: string | null;
|
||||||
|
happLink?: string | null;
|
||||||
|
fallbackUrl?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveConnectionUrlForUi(input: ResolveConnectionUrlInput): string | null {
|
||||||
|
const defaultUrl =
|
||||||
|
input.fallbackUrl ?? input.subscriptionUrl ?? input.displayLink ?? input.happSchemeLink ?? null;
|
||||||
|
|
||||||
|
if (!isHappCryptolinkMode(input.mode)) return defaultUrl;
|
||||||
|
|
||||||
|
const backendCryptLink =
|
||||||
|
[
|
||||||
|
input.happCryptLink,
|
||||||
|
input.happCryptoLink,
|
||||||
|
input.happLink,
|
||||||
|
input.happSchemeLink,
|
||||||
|
input.displayLink,
|
||||||
|
input.subscriptionUrl,
|
||||||
|
].find((value) => isHappCryptDeepLink(value)) ?? null;
|
||||||
|
if (backendCryptLink) return backendCryptLink;
|
||||||
|
|
||||||
|
const sourceSubscriptionUrl =
|
||||||
|
[input.subscriptionUrl, input.displayLink, input.fallbackUrl].find((value) =>
|
||||||
|
isCryptSourceUrl(value),
|
||||||
|
) ?? null;
|
||||||
|
|
||||||
|
if (sourceSubscriptionUrl) {
|
||||||
|
return (
|
||||||
|
createHappCryptoLink(sourceSubscriptionUrl, 'v4', true) ??
|
||||||
|
createHappCryptoLink(sourceSubscriptionUrl, 'v3', true) ??
|
||||||
|
defaultUrl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultUrl;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user