fix mini app bot father
This commit is contained in:
@@ -2,21 +2,20 @@
|
|||||||
|
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { Check, Copy, Loader2, Bot } from 'lucide-react';
|
import { Check, Copy, Loader2, Bot } from 'lucide-react';
|
||||||
import { useAuth } from '@/components/id/auth-provider';
|
|
||||||
import { useToast } from '@/components/id/toast-provider';
|
import { useToast } from '@/components/id/toast-provider';
|
||||||
|
import { usePublicSettings } from '@/components/id/public-settings-provider';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { createManagedBot, getApiErrorMessage } from '@/lib/api';
|
import { createManagedBot, getApiErrorMessage } from '@/lib/api';
|
||||||
import { useEmbeddedAuthFallback } from '@/lib/embedded-auth-bridge';
|
import { useMiniAppAuth } from '@/hooks/use-mini-app-auth';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
type Step = 'name' | 'username' | 'done';
|
type Step = 'name' | 'username' | 'done';
|
||||||
|
|
||||||
export function BotCreateMiniAppContent() {
|
export function BotCreateMiniAppContent() {
|
||||||
const { token, isPinLocked, isLoading } = useAuth();
|
const { projectName } = usePublicSettings();
|
||||||
const embeddedToken = useEmbeddedAuthFallback();
|
const { effectiveToken, canUse, authReady, waitingForBridge, isLoading } = useMiniAppAuth();
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
const effectiveToken = token ?? embeddedToken;
|
|
||||||
|
|
||||||
const [step, setStep] = useState<Step>('name');
|
const [step, setStep] = useState<Step>('name');
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
@@ -24,11 +23,11 @@ export function BotCreateMiniAppContent() {
|
|||||||
const [creating, setCreating] = useState(false);
|
const [creating, setCreating] = useState(false);
|
||||||
const [createdBot, setCreatedBot] = useState<{ username: string; token: string; botId: string } | null>(null);
|
const [createdBot, setCreatedBot] = useState<{ username: string; token: string; botId: string } | null>(null);
|
||||||
|
|
||||||
const canUse = Boolean(effectiveToken && !isPinLocked);
|
const canUseSession = canUse;
|
||||||
const usernamePreview = useMemo(() => `${username.replace(/_bot$/i, '').trim()}_bot`, [username]);
|
const usernamePreview = useMemo(() => `${username.replace(/_bot$/i, '').trim()}_bot`, [username]);
|
||||||
|
|
||||||
async function handleCreate() {
|
async function handleCreate() {
|
||||||
if (!canUse || !effectiveToken) return;
|
if (!canUseSession || !effectiveToken) return;
|
||||||
const trimmedName = name.trim();
|
const trimmedName = name.trim();
|
||||||
const trimmedUsername = username.replace(/_bot$/i, '').trim();
|
const trimmedUsername = username.replace(/_bot$/i, '').trim();
|
||||||
if (!trimmedName) {
|
if (!trimmedName) {
|
||||||
@@ -64,7 +63,7 @@ export function BotCreateMiniAppContent() {
|
|||||||
showToast('Токен скопирован');
|
showToast('Токен скопирован');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading && !effectiveToken) {
|
if ((isLoading || waitingForBridge || !authReady) && !effectiveToken) {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center p-6 text-center text-sm text-[#667085]">
|
<div className="flex min-h-screen items-center justify-center p-6 text-center text-sm text-[#667085]">
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
@@ -73,10 +72,10 @@ export function BotCreateMiniAppContent() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canUse) {
|
if (!canUseSession) {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center p-6 text-center text-sm text-[#667085]">
|
<div className="flex min-h-screen items-center justify-center p-6 text-center text-sm text-[#667085]">
|
||||||
Войдите в Lendry ID, чтобы создать бота
|
Войдите в {projectName}, чтобы создать бота
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { Copy, ImageIcon, LayoutGrid, Loader2, RefreshCw, TextQuote, UserCircle2 } from 'lucide-react';
|
import { Copy, ImageIcon, LayoutGrid, Loader2, RefreshCw, TextQuote, UserCircle2 } from 'lucide-react';
|
||||||
import { useAuth } from '@/components/id/auth-provider';
|
import { usePublicSettings } from '@/components/id/public-settings-provider';
|
||||||
import { useToast } from '@/components/id/toast-provider';
|
import { useToast } from '@/components/id/toast-provider';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
updateManagedBot,
|
updateManagedBot,
|
||||||
updateManagedBotProfile
|
updateManagedBotProfile
|
||||||
} from '@/lib/api';
|
} from '@/lib/api';
|
||||||
import { useEmbeddedAuthFallback } from '@/lib/embedded-auth-bridge';
|
import { useMiniAppAuth } from '@/hooks/use-mini-app-auth';
|
||||||
import { parseComposerMenuButtonJson } from '@/lib/bot-menu-button';
|
import { parseComposerMenuButtonJson } from '@/lib/bot-menu-button';
|
||||||
|
|
||||||
function FieldTextarea({
|
function FieldTextarea({
|
||||||
@@ -78,9 +78,8 @@ function SectionCard({
|
|||||||
export function BotManageMiniAppContent() {
|
export function BotManageMiniAppContent() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const botId = searchParams.get('botId') ?? '';
|
const botId = searchParams.get('botId') ?? '';
|
||||||
const { token, isPinLocked, isLoading } = useAuth();
|
const { projectName } = usePublicSettings();
|
||||||
const embeddedToken = useEmbeddedAuthFallback();
|
const { effectiveToken, canUse: canUseSession, authReady, waitingForBridge, isLoading } = useMiniAppAuth();
|
||||||
const effectiveToken = token ?? embeddedToken;
|
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -97,7 +96,7 @@ export function BotManageMiniAppContent() {
|
|||||||
const [tokenPrefix, setTokenPrefix] = useState('');
|
const [tokenPrefix, setTokenPrefix] = useState('');
|
||||||
const [newToken, setNewToken] = useState<string | null>(null);
|
const [newToken, setNewToken] = useState<string | null>(null);
|
||||||
|
|
||||||
const canUse = Boolean(effectiveToken && !isPinLocked && botId);
|
const canUse = Boolean(canUseSession && botId);
|
||||||
|
|
||||||
const loadBot = useCallback(async () => {
|
const loadBot = useCallback(async () => {
|
||||||
if (!canUse || !effectiveToken) return;
|
if (!canUse || !effectiveToken) return;
|
||||||
@@ -219,10 +218,19 @@ export function BotManageMiniAppContent() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((waitingForBridge || !authReady) && !effectiveToken) {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen items-center justify-center p-6 text-center text-sm text-[#667085]">
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
Загрузка...
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!canUse) {
|
if (!canUse) {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center p-6 text-center text-sm text-[#667085]">
|
<div className="flex min-h-screen items-center justify-center p-6 text-center text-sm text-[#667085]">
|
||||||
Войдите в Lendry ID, чтобы управлять ботом
|
Войдите в {projectName}, чтобы управлять ботом
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import {
|
|||||||
fetchBotChatMessages,
|
fetchBotChatMessages,
|
||||||
sendBotMessage
|
sendBotMessage
|
||||||
} from '@/lib/api';
|
} from '@/lib/api';
|
||||||
import { postEmbeddedAuthToIframe } from '@/lib/embedded-auth-bridge';
|
import { postEmbeddedAuthToIframe, EMBEDDED_AUTH_REQUEST } from '@/lib/embedded-auth-bridge';
|
||||||
|
import { AUTH_REFRESH_KEY, AUTH_SESSION_KEY, AUTH_TOKEN_KEY } from '@/lib/api';
|
||||||
|
import { normalizeMiniAppUrl, resolveMiniAppTargetOrigin } from '@/lib/mini-app-url';
|
||||||
import { extractMessageReplyMarkup, serializeInlineKeyboardMarkup } from '@/lib/bot-reply-markup';
|
import { extractMessageReplyMarkup, serializeInlineKeyboardMarkup } from '@/lib/bot-reply-markup';
|
||||||
import { resolveComposerMenuButton, type ComposerMenuButton } from '@/lib/bot-menu-button';
|
import { resolveComposerMenuButton, type ComposerMenuButton } from '@/lib/bot-menu-button';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
@@ -305,27 +307,44 @@ export function BotMiniAppSheet({
|
|||||||
onClose
|
onClose
|
||||||
}: BotMiniAppSheetProps) {
|
}: BotMiniAppSheetProps) {
|
||||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||||
|
const normalizedUrl = url ? normalizeMiniAppUrl(url) : null;
|
||||||
|
const resolvedAuthToken =
|
||||||
|
authToken ?? (typeof window !== 'undefined' ? window.localStorage.getItem(AUTH_TOKEN_KEY) : null);
|
||||||
|
|
||||||
const pushAuthToIframe = useCallback(() => {
|
const pushAuthToIframe = useCallback(() => {
|
||||||
if (!iframeRef.current || !authToken) return;
|
if (!iframeRef.current || !resolvedAuthToken) return;
|
||||||
postEmbeddedAuthToIframe(iframeRef.current, {
|
postEmbeddedAuthToIframe(iframeRef.current, {
|
||||||
token: authToken,
|
token: resolvedAuthToken,
|
||||||
refreshToken: authRefreshToken,
|
refreshToken: authRefreshToken ?? window.localStorage.getItem(AUTH_REFRESH_KEY),
|
||||||
sessionId: authSessionId
|
sessionId: authSessionId ?? window.localStorage.getItem(AUTH_SESSION_KEY)
|
||||||
});
|
});
|
||||||
}, [authRefreshToken, authSessionId, authToken]);
|
}, [authRefreshToken, authSessionId, resolvedAuthToken]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleMessage(event: MessageEvent) {
|
function handleMessage(event: MessageEvent) {
|
||||||
if (event.origin !== window.location.origin) return;
|
const iframeOrigin = iframeRef.current
|
||||||
if (event.data?.type !== 'lendry-id-embedded-auth-request') return;
|
? resolveMiniAppTargetOrigin(iframeRef.current.getAttribute('src') || iframeRef.current.src, window.location.origin)
|
||||||
|
: window.location.origin;
|
||||||
|
if (event.origin !== iframeOrigin && event.origin !== window.location.origin) return;
|
||||||
|
if (event.data?.type !== EMBEDDED_AUTH_REQUEST) return;
|
||||||
pushAuthToIframe();
|
pushAuthToIframe();
|
||||||
}
|
}
|
||||||
window.addEventListener('message', handleMessage);
|
window.addEventListener('message', handleMessage);
|
||||||
return () => window.removeEventListener('message', handleMessage);
|
return () => window.removeEventListener('message', handleMessage);
|
||||||
}, [pushAuthToIframe]);
|
}, [pushAuthToIframe]);
|
||||||
|
|
||||||
if (!url) return null;
|
useEffect(() => {
|
||||||
|
if (!normalizedUrl || !resolvedAuthToken) return;
|
||||||
|
pushAuthToIframe();
|
||||||
|
const interval = window.setInterval(pushAuthToIframe, 350);
|
||||||
|
const timeout = window.setTimeout(() => window.clearInterval(interval), 3500);
|
||||||
|
return () => {
|
||||||
|
window.clearInterval(interval);
|
||||||
|
window.clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
}, [normalizedUrl, pushAuthToIframe, resolvedAuthToken]);
|
||||||
|
|
||||||
|
if (!normalizedUrl) return null;
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[70] flex items-end justify-center bg-black/40 p-4 sm:items-center">
|
<div className="fixed inset-0 z-[70] flex items-end justify-center bg-black/40 p-4 sm:items-center">
|
||||||
<div className="flex h-[min(88vh,720px)] w-full max-w-[520px] flex-col overflow-hidden rounded-[24px] bg-white shadow-2xl">
|
<div className="flex h-[min(88vh,720px)] w-full max-w-[520px] flex-col overflow-hidden rounded-[24px] bg-white shadow-2xl">
|
||||||
@@ -337,7 +356,7 @@ export function BotMiniAppSheet({
|
|||||||
</div>
|
</div>
|
||||||
<iframe
|
<iframe
|
||||||
ref={iframeRef}
|
ref={iframeRef}
|
||||||
src={url}
|
src={normalizedUrl}
|
||||||
title={title}
|
title={title}
|
||||||
className="min-h-0 flex-1 border-0"
|
className="min-h-0 flex-1 border-0"
|
||||||
allow="clipboard-read; clipboard-write"
|
allow="clipboard-read; clipboard-write"
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
} from '@/lib/api';
|
} from '@/lib/api';
|
||||||
import { useToast } from './toast-provider';
|
import { useToast } from './toast-provider';
|
||||||
import { PinLockModal } from './pin-lock-modal';
|
import { PinLockModal } from './pin-lock-modal';
|
||||||
|
import { EMBEDDED_AUTH_MESSAGE, type EmbeddedAuthPayload } from '@/lib/embedded-auth-bridge';
|
||||||
|
|
||||||
interface AuthContextValue {
|
interface AuthContextValue {
|
||||||
user: PublicUser | null;
|
user: PublicUser | null;
|
||||||
@@ -117,6 +118,19 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
resetPinRequiredNotification();
|
resetPinRequiredNotification();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
function handleEmbeddedAuth(event: Event) {
|
||||||
|
const detail = (event as CustomEvent<EmbeddedAuthPayload>).detail;
|
||||||
|
if (!detail?.token?.trim()) return;
|
||||||
|
setToken(detail.token.trim());
|
||||||
|
setHasStoredSession(true);
|
||||||
|
clearPinLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener(EMBEDDED_AUTH_MESSAGE, handleEmbeddedAuth as EventListener);
|
||||||
|
return () => window.removeEventListener(EMBEDDED_AUTH_MESSAGE, handleEmbeddedAuth as EventListener);
|
||||||
|
}, [clearPinLock]);
|
||||||
|
|
||||||
const activatePinLock = React.useCallback((sessionId: string) => {
|
const activatePinLock = React.useCallback((sessionId: string) => {
|
||||||
const resolvedSessionId = sessionId || window.localStorage.getItem(AUTH_SESSION_KEY);
|
const resolvedSessionId = sessionId || window.localStorage.getItem(AUTH_SESSION_KEY);
|
||||||
if (!resolvedSessionId) return;
|
if (!resolvedSessionId) return;
|
||||||
|
|||||||
37
apps/frontend/hooks/use-mini-app-auth.ts
Normal file
37
apps/frontend/hooks/use-mini-app-auth.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useAuth } from '@/components/id/auth-provider';
|
||||||
|
import { useEmbeddedAuthFallback } from '@/lib/embedded-auth-bridge';
|
||||||
|
|
||||||
|
const EMBEDDED_AUTH_WAIT_MS = 4000;
|
||||||
|
|
||||||
|
export function useMiniAppAuth() {
|
||||||
|
const { token, isPinLocked, isLoading } = useAuth();
|
||||||
|
const embeddedToken = useEmbeddedAuthFallback();
|
||||||
|
const isEmbedded = typeof window !== 'undefined' && window.parent !== window;
|
||||||
|
const [bridgeTimedOut, setBridgeTimedOut] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isEmbedded) {
|
||||||
|
setBridgeTimedOut(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const timer = window.setTimeout(() => setBridgeTimedOut(true), EMBEDDED_AUTH_WAIT_MS);
|
||||||
|
return () => window.clearTimeout(timer);
|
||||||
|
}, [isEmbedded]);
|
||||||
|
|
||||||
|
const effectiveToken = token ?? embeddedToken;
|
||||||
|
const waitingForBridge = isEmbedded && !effectiveToken && !bridgeTimedOut;
|
||||||
|
const authReady = !isEmbedded || Boolean(effectiveToken) || bridgeTimedOut || !isLoading;
|
||||||
|
const canUse = Boolean(effectiveToken && (!isPinLocked || Boolean(embeddedToken)));
|
||||||
|
|
||||||
|
return {
|
||||||
|
effectiveToken,
|
||||||
|
canUse,
|
||||||
|
authReady,
|
||||||
|
waitingForBridge,
|
||||||
|
isEmbedded,
|
||||||
|
isLoading
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { AUTH_REFRESH_KEY, AUTH_SESSION_KEY, AUTH_TOKEN_KEY } from '@/lib/api';
|
import { AUTH_REFRESH_KEY, AUTH_SESSION_KEY, AUTH_TOKEN_KEY } from '@/lib/api';
|
||||||
|
import { resolveMiniAppTargetOrigin } from '@/lib/mini-app-url';
|
||||||
|
|
||||||
export const EMBEDDED_AUTH_MESSAGE = 'lendry-id-embedded-auth';
|
export const EMBEDDED_AUTH_MESSAGE = 'lendry-id-embedded-auth';
|
||||||
|
export const EMBEDDED_AUTH_REQUEST = 'lendry-id-embedded-auth-request';
|
||||||
|
|
||||||
export interface EmbeddedAuthPayload {
|
export interface EmbeddedAuthPayload {
|
||||||
type: typeof EMBEDDED_AUTH_MESSAGE;
|
type: typeof EMBEDDED_AUTH_MESSAGE;
|
||||||
@@ -12,16 +14,57 @@ export interface EmbeddedAuthPayload {
|
|||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postEmbeddedAuthToIframe(iframe: HTMLIFrameElement, auth: { token: string; refreshToken?: string | null; sessionId?: string | null }) {
|
function collectTrustedOrigins() {
|
||||||
|
const origins = new Set<string>();
|
||||||
|
if (typeof window === 'undefined') return origins;
|
||||||
|
|
||||||
|
origins.add(window.location.origin);
|
||||||
|
if (document.referrer) {
|
||||||
|
try {
|
||||||
|
origins.add(new URL(document.referrer).origin);
|
||||||
|
} catch {
|
||||||
|
// ignore malformed referrer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return origins;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveParentPostMessageTarget() {
|
||||||
|
if (typeof window === 'undefined' || window.parent === window) {
|
||||||
|
return window.location.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.referrer) {
|
||||||
|
try {
|
||||||
|
const referrerOrigin = new URL(document.referrer).origin;
|
||||||
|
if (referrerOrigin !== window.location.origin) {
|
||||||
|
return '*';
|
||||||
|
}
|
||||||
|
return referrerOrigin;
|
||||||
|
} catch {
|
||||||
|
return '*';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '*';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postEmbeddedAuthToIframe(
|
||||||
|
iframe: HTMLIFrameElement,
|
||||||
|
auth: { token: string; refreshToken?: string | null; sessionId?: string | null }
|
||||||
|
) {
|
||||||
const target = iframe.contentWindow;
|
const target = iframe.contentWindow;
|
||||||
if (!target || !auth.token) return;
|
if (!target || !auth.token) return;
|
||||||
|
|
||||||
const payload: EmbeddedAuthPayload = {
|
const payload: EmbeddedAuthPayload = {
|
||||||
type: EMBEDDED_AUTH_MESSAGE,
|
type: EMBEDDED_AUTH_MESSAGE,
|
||||||
token: auth.token,
|
token: auth.token,
|
||||||
refreshToken: auth.refreshToken ?? undefined,
|
refreshToken: auth.refreshToken ?? undefined,
|
||||||
sessionId: auth.sessionId ?? undefined
|
sessionId: auth.sessionId ?? undefined
|
||||||
};
|
};
|
||||||
target.postMessage(payload, window.location.origin);
|
|
||||||
|
const targetOrigin = resolveMiniAppTargetOrigin(iframe.getAttribute('src') || iframe.src, window.location.origin);
|
||||||
|
target.postMessage(payload, targetOrigin);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEmbeddedAuthFallback() {
|
export function useEmbeddedAuthFallback() {
|
||||||
@@ -29,7 +72,7 @@ export function useEmbeddedAuthFallback() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleMessage(event: MessageEvent) {
|
function handleMessage(event: MessageEvent) {
|
||||||
if (event.origin !== window.location.origin) return;
|
if (!collectTrustedOrigins().has(event.origin)) return;
|
||||||
const data = event.data as Partial<EmbeddedAuthPayload> | null;
|
const data = event.data as Partial<EmbeddedAuthPayload> | null;
|
||||||
if (!data || data.type !== EMBEDDED_AUTH_MESSAGE || !data.token?.trim()) return;
|
if (!data || data.type !== EMBEDDED_AUTH_MESSAGE || !data.token?.trim()) return;
|
||||||
|
|
||||||
@@ -42,11 +85,12 @@ export function useEmbeddedAuthFallback() {
|
|||||||
if (data.sessionId) {
|
if (data.sessionId) {
|
||||||
window.localStorage.setItem(AUTH_SESSION_KEY, data.sessionId);
|
window.localStorage.setItem(AUTH_SESSION_KEY, data.sessionId);
|
||||||
}
|
}
|
||||||
|
window.dispatchEvent(new CustomEvent(EMBEDDED_AUTH_MESSAGE, { detail: data }));
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('message', handleMessage);
|
window.addEventListener('message', handleMessage);
|
||||||
if (window.parent !== window) {
|
if (window.parent !== window) {
|
||||||
window.parent.postMessage({ type: 'lendry-id-embedded-auth-request' }, window.location.origin);
|
window.parent.postMessage({ type: EMBEDDED_AUTH_REQUEST }, resolveParentPostMessageTarget());
|
||||||
}
|
}
|
||||||
return () => window.removeEventListener('message', handleMessage);
|
return () => window.removeEventListener('message', handleMessage);
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
22
apps/frontend/lib/mini-app-url.ts
Normal file
22
apps/frontend/lib/mini-app-url.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export function normalizeMiniAppUrl(url: string) {
|
||||||
|
if (typeof window === 'undefined') return url;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = new URL(url, window.location.href);
|
||||||
|
if (parsed.pathname.startsWith('/mini-apps/')) {
|
||||||
|
return `${window.location.origin}${parsed.pathname}${parsed.search}${parsed.hash}`;
|
||||||
|
}
|
||||||
|
return parsed.toString();
|
||||||
|
} catch {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveMiniAppTargetOrigin(iframeSrc: string | null | undefined, fallback: string) {
|
||||||
|
if (!iframeSrc) return fallback;
|
||||||
|
try {
|
||||||
|
return new URL(iframeSrc, fallback).origin;
|
||||||
|
} catch {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user