214 lines
7.0 KiB
TypeScript
214 lines
7.0 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import { useSession } from 'next-auth/react';
|
|
|
|
export interface ResponsiveIframeProps {
|
|
src: string;
|
|
title?: string;
|
|
token?: string;
|
|
className?: string;
|
|
style?: React.CSSProperties;
|
|
hideUntilLoad?: boolean;
|
|
allowFullScreen?: boolean;
|
|
scrolling?: boolean;
|
|
heightOffset?: number;
|
|
}
|
|
|
|
// Map of service prefixes to their base URLs - keep in sync with proxy route.ts
|
|
const SERVICE_URLS: Record<string, string> = {
|
|
'parole': process.env.NEXT_PUBLIC_IFRAME_PAROLE_URL || '',
|
|
'alma': process.env.NEXT_PUBLIC_IFRAME_AI_ASSISTANT_URL || '',
|
|
'crm': process.env.NEXT_PUBLIC_IFRAME_MEDIATIONS_URL || '',
|
|
'vision': process.env.NEXT_PUBLIC_IFRAME_CONFERENCE_URL || '',
|
|
'showcase': process.env.NEXT_PUBLIC_IFRAME_SHOWCASE_URL || '',
|
|
'agilite': process.env.NEXT_PUBLIC_IFRAME_AGILITY_URL || '',
|
|
'dossiers': process.env.NEXT_PUBLIC_IFRAME_DRIVE_URL || '',
|
|
'the-message': process.env.NEXT_PUBLIC_IFRAME_THEMESSAGE_URL || '',
|
|
'qg': process.env.NEXT_PUBLIC_IFRAME_MISSIONVIEW_URL || '',
|
|
'design': process.env.NEXT_PUBLIC_IFRAME_DESIGN_URL || '',
|
|
};
|
|
|
|
export default function ResponsiveIframe({
|
|
src,
|
|
title = 'Embedded content',
|
|
token,
|
|
className = '',
|
|
style = {},
|
|
hideUntilLoad = false,
|
|
allowFullScreen = false,
|
|
scrolling = true,
|
|
heightOffset = 0,
|
|
}: ResponsiveIframeProps) {
|
|
const [height, setHeight] = useState<number>(0);
|
|
const [loaded, setLoaded] = useState<boolean>(false);
|
|
const [authError, setAuthError] = useState<boolean>(false);
|
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
const silentAuthRef = useRef<HTMLIFrameElement>(null);
|
|
const silentAuthTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
const { data: session } = useSession();
|
|
|
|
// Convert proxy URLs to direct URLs to avoid double proxying
|
|
const processedSrc = (() => {
|
|
if (src.startsWith('/api/proxy/')) {
|
|
// Extract the service name and path
|
|
const parts = src.replace('/api/proxy/', '').split('/');
|
|
const serviceName = parts[0];
|
|
const path = parts.slice(1).join('/');
|
|
|
|
// Look up the base URL for this service
|
|
const baseUrl = SERVICE_URLS[serviceName];
|
|
if (baseUrl) {
|
|
console.log(`Converting proxy URL to direct URL: ${src} -> ${baseUrl}/${path}`);
|
|
return `${baseUrl}/${path}`;
|
|
}
|
|
}
|
|
return src;
|
|
})();
|
|
|
|
// Append token to src if provided
|
|
const fullSrc = token ? `${processedSrc}${processedSrc.includes('?') ? '&' : '?'}token=${token}` : processedSrc;
|
|
|
|
// Handle silent authentication refresh
|
|
useEffect(() => {
|
|
// Setup silent authentication check every 15 minutes
|
|
const setupSilentAuth = () => {
|
|
if (silentAuthTimerRef.current) {
|
|
clearTimeout(silentAuthTimerRef.current);
|
|
}
|
|
|
|
// Create a hidden iframe to check authentication status
|
|
silentAuthTimerRef.current = setTimeout(() => {
|
|
console.log('Running silent authentication check');
|
|
|
|
// Create the silent auth iframe if it doesn't exist
|
|
if (silentAuthRef.current && !silentAuthRef.current.src) {
|
|
silentAuthRef.current.src = '/silent-refresh';
|
|
}
|
|
|
|
// Setup next check
|
|
setupSilentAuth();
|
|
}, 15 * 60 * 1000); // 15 minutes
|
|
};
|
|
|
|
// Handle messages from the silent auth iframe
|
|
const handleAuthMessage = (event: MessageEvent) => {
|
|
if (event.data && event.data.type === 'AUTH_STATUS') {
|
|
console.log('Received auth status:', event.data);
|
|
|
|
if (event.data.status === 'UNAUTHENTICATED') {
|
|
console.error('Silent authentication failed - user is not authenticated');
|
|
setAuthError(true);
|
|
|
|
// Force immediate refresh
|
|
if (silentAuthRef.current) {
|
|
silentAuthRef.current.src = '/silent-refresh';
|
|
}
|
|
} else if (event.data.status === 'AUTHENTICATED') {
|
|
console.log('Silent authentication successful');
|
|
setAuthError(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
window.addEventListener('message', handleAuthMessage);
|
|
setupSilentAuth();
|
|
|
|
// Cleanup
|
|
return () => {
|
|
window.removeEventListener('message', handleAuthMessage);
|
|
if (silentAuthTimerRef.current) {
|
|
clearTimeout(silentAuthTimerRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
// Adjust iframe height based on window size
|
|
useEffect(() => {
|
|
const updateHeight = () => {
|
|
const viewportHeight = window.innerHeight;
|
|
const newHeight = viewportHeight - heightOffset;
|
|
setHeight(newHeight > 0 ? newHeight : 0);
|
|
};
|
|
|
|
updateHeight();
|
|
window.addEventListener('resize', updateHeight);
|
|
|
|
return () => {
|
|
window.removeEventListener('resize', updateHeight);
|
|
};
|
|
}, [heightOffset]);
|
|
|
|
// Handle hash changes by updating iframe source
|
|
useEffect(() => {
|
|
const handleHashChange = () => {
|
|
const iframe = iframeRef.current;
|
|
if (iframe && iframe.src) {
|
|
const url = new URL(iframe.src);
|
|
|
|
// If there's a hash in the parent window's URL
|
|
if (window.location.hash) {
|
|
url.hash = window.location.hash;
|
|
iframe.src = url.toString();
|
|
}
|
|
}
|
|
};
|
|
|
|
window.addEventListener('hashchange', handleHashChange);
|
|
return () => {
|
|
window.removeEventListener('hashchange', handleHashChange);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<>
|
|
{/* Hidden iframe for silent authentication */}
|
|
<iframe
|
|
ref={silentAuthRef}
|
|
style={{ display: 'none' }}
|
|
title="Silent Authentication"
|
|
/>
|
|
|
|
{/* Main content iframe */}
|
|
<div className="relative w-full h-full">
|
|
{authError && (
|
|
<div className="absolute inset-0 flex items-center justify-center bg-red-50 bg-opacity-90 z-10">
|
|
<div className="text-center p-4">
|
|
<p className="text-red-600 font-semibold">Session expired or authentication error</p>
|
|
<button
|
|
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
onClick={() => window.location.href = '/api/auth/signin?callbackUrl=' + encodeURIComponent(window.location.href)}
|
|
>
|
|
Sign in again
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<iframe
|
|
ref={iframeRef}
|
|
src={fullSrc}
|
|
title={title}
|
|
className={`w-full ${className}`}
|
|
style={{
|
|
height: height > 0 ? `${height}px` : '100%',
|
|
border: 'none',
|
|
visibility: hideUntilLoad && !loaded ? 'hidden' : 'visible',
|
|
...style,
|
|
}}
|
|
onLoad={() => {
|
|
setLoaded(true);
|
|
}}
|
|
allowFullScreen={allowFullScreen}
|
|
scrolling={scrolling ? 'yes' : 'no'}
|
|
/>
|
|
|
|
{hideUntilLoad && !loaded && (
|
|
<div className="flex justify-center items-center w-full h-full absolute top-0 left-0">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|