Neah/app/components/responsive-iframe.tsx
2025-05-02 12:33:43 +02:00

157 lines
4.7 KiB
TypeScript

'use client';
import { useEffect, useRef, useState } from 'react';
import { useSession } from 'next-auth/react';
interface ResponsiveIframeProps {
src: string;
className?: string;
allow?: string;
style?: React.CSSProperties;
token?: string;
}
export function ResponsiveIframe({ src, className = '', allow, style, token }: ResponsiveIframeProps) {
const iframeRef = useRef<HTMLIFrameElement>(null);
const { data: session } = useSession();
const [authError, setAuthError] = useState<string | null>(null);
// Add token parameter only if token is provided
const fullSrc = token ?
`${src}${src.includes('?') ? '&' : '?'}token=${encodeURIComponent(token)}` :
src;
// Handle silent authentication refresh
useEffect(() => {
let silentRefreshTimer: NodeJS.Timeout;
// Set up periodic silent refresh (every 15 minutes)
const startSilentRefresh = () => {
silentRefreshTimer = setInterval(() => {
console.log('Performing silent authentication check for iframes');
// Create a hidden iframe for silent authentication
const refreshFrame = document.createElement('iframe');
refreshFrame.style.display = 'none';
refreshFrame.src = '/silent-refresh';
document.body.appendChild(refreshFrame);
// Remove iframe after it has loaded (5 seconds timeout)
setTimeout(() => {
if (refreshFrame && refreshFrame.parentNode) {
refreshFrame.parentNode.removeChild(refreshFrame);
}
}, 5000);
}, 15 * 60 * 1000); // 15 minutes
};
if (session) {
startSilentRefresh();
}
return () => {
if (silentRefreshTimer) {
clearInterval(silentRefreshTimer);
}
};
}, [session]);
useEffect(() => {
const iframe = iframeRef.current;
if (!iframe) return;
const calculateHeight = () => {
const pageY = (elem: HTMLElement): number => {
return elem.offsetParent ?
(elem.offsetTop + pageY(elem.offsetParent as HTMLElement)) :
elem.offsetTop;
};
const height = document.documentElement.clientHeight;
const iframeY = pageY(iframe);
const newHeight = Math.max(0, height - iframeY);
iframe.style.height = `${newHeight}px`;
};
const handleHashChange = () => {
if (window.location.hash && window.location.hash.length) {
const iframeURL = new URL(iframe.src);
iframeURL.hash = window.location.hash;
iframe.src = iframeURL.toString();
}
};
// Handle authentication messages from iframe
const handleMessage = (event: MessageEvent) => {
// Accept messages from our iframe or from silent auth iframe
if (event.source !== iframe.contentWindow &&
!event.data?.type?.startsWith('SILENT_AUTH_')) return;
const { type, data, error } = event.data || {};
// Handle auth-related messages
if (type === 'AUTH_ERROR' || type === 'SESSION_EXPIRED') {
console.log('Auth error in iframe:', data || error);
setAuthError(error || 'Authentication error');
} else if (type === 'SILENT_AUTH_SUCCESS') {
console.log('Silent authentication successful');
setAuthError(null);
} else if (type === 'SILENT_AUTH_FAILURE') {
console.log('Silent authentication failed:', error);
// Only set error if it's persistent
if (error !== 'loading') {
setAuthError('Session expired');
}
}
};
// Initial setup
calculateHeight();
handleHashChange();
// Event listeners
window.addEventListener('resize', calculateHeight);
window.addEventListener('hashchange', handleHashChange);
window.addEventListener('message', handleMessage);
iframe.addEventListener('load', calculateHeight);
// Cleanup
return () => {
window.removeEventListener('resize', calculateHeight);
window.removeEventListener('hashchange', handleHashChange);
window.removeEventListener('message', handleMessage);
iframe.removeEventListener('load', calculateHeight);
};
}, []);
return (
<>
{authError && (
<div
style={{
backgroundColor: 'rgba(255, 0, 0, 0.1)',
padding: '10px',
borderRadius: '4px',
marginBottom: '10px'
}}
>
Authentication error: {authError}. The service might not work correctly.
</div>
)}
<iframe
ref={iframeRef}
id="myFrame"
src={fullSrc}
className={`w-full border-none ${className}`}
style={{
display: 'block',
width: '100%',
height: '100%',
...style
}}
allow={allow}
allowFullScreen
/>
</>
);
}