ci: docker_tag=ext fix(front): rendre SSR-safe Iframe/MessageBus/AuthModal/_app (basePath /lecoffre)
This commit is contained in:
parent
22bcc727c9
commit
5284d9be04
@ -36,6 +36,10 @@ export default class WindowStore {
|
||||
}
|
||||
|
||||
private iniEvents(): void {
|
||||
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
||||
// SSR: do not bind browser events
|
||||
return;
|
||||
}
|
||||
window.addEventListener("scroll", (e: Event) => this.scrollYHandler());
|
||||
window.addEventListener("resize", (e: Event) => this.resizeHandler());
|
||||
document.addEventListener("click", (e: MouseEvent) => this.clickHandler(e), true);
|
||||
@ -46,10 +50,11 @@ export default class WindowStore {
|
||||
}
|
||||
|
||||
private scrollYHandler = (() => {
|
||||
let previousY: number = window.scrollY;
|
||||
let previousY: number = (typeof window !== 'undefined' ? window.scrollY : 0);
|
||||
let snapShotY: number = previousY;
|
||||
let previousYDirection: number = 1;
|
||||
return (): void => {
|
||||
if (typeof window === 'undefined') return;
|
||||
const scrollYDirection = window.scrollY - previousY > 0 ? 1 : -1;
|
||||
if (previousYDirection !== scrollYDirection) {
|
||||
snapShotY = window.scrollY;
|
||||
@ -62,6 +67,7 @@ export default class WindowStore {
|
||||
})();
|
||||
|
||||
private resizeHandler() {
|
||||
if (typeof window === 'undefined') return;
|
||||
this.event.emit("resize", window);
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,13 @@ import type { AppType, AppProps } from "next/app";
|
||||
import { useEffect, useState, type ReactElement, type ReactNode } from "react";
|
||||
import getConfig from "next/config";
|
||||
import { GoogleTagManager } from "@next/third-parties/google";
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import Loader from "src/common/Api/LeCoffreApi/sdk/Loader";
|
||||
|
||||
import IframeReference from "src/sdk/IframeReference";
|
||||
import Iframe from "src/sdk/Iframe";
|
||||
// import Iframe from "src/sdk/Iframe";
|
||||
const IframeNoSSR = dynamic(() => import('src/sdk/Iframe'), { ssr: false });
|
||||
import MessageBus from "src/sdk/MessageBus";
|
||||
import User from "src/sdk/User";
|
||||
|
||||
@ -80,26 +82,31 @@ const MyApp = (({
|
||||
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
const targetOrigin = (() => {
|
||||
try {
|
||||
return new URL(_4nkUrl).origin;
|
||||
} catch {
|
||||
return _4nkUrl;
|
||||
}
|
||||
})();
|
||||
IframeReference.setTargetOrigin(targetOrigin);
|
||||
// Configure iframe target and URL on client-side only
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
try {
|
||||
if (_4nkUrl && typeof _4nkUrl === 'string' && _4nkUrl.trim().length > 0) {
|
||||
const origin = (() => {
|
||||
try { return new URL(_4nkUrl).origin; } catch { return _4nkUrl; }
|
||||
})();
|
||||
IframeReference.setTargetOrigin(origin);
|
||||
|
||||
// Configure full iframe URL if provided (falls back to origin)
|
||||
const iframeUrl = (() => {
|
||||
const candidate = (publicRuntimeConfig as any).NEXT_PUBLIC_4NK_IFRAME_URL ?? _4nkUrl;
|
||||
try {
|
||||
return new URL(candidate).toString();
|
||||
} catch {
|
||||
return targetOrigin;
|
||||
}
|
||||
})();
|
||||
IframeReference.setIframeUrl(iframeUrl);
|
||||
const candidate = (publicRuntimeConfig as any).NEXT_PUBLIC_4NK_IFRAME_URL ?? _4nkUrl;
|
||||
const iframe = (() => {
|
||||
try { return new URL(candidate).toString(); } catch { return origin; }
|
||||
})();
|
||||
IframeReference.setIframeUrl(iframe);
|
||||
} else {
|
||||
console.warn('[MyApp] NEXT_PUBLIC_4NK_URL is missing; skipping iframe setup');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[MyApp] Failed to initialize IframeReference', e);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const isAuthenticated = User.getInstance().isAuthenticated();
|
||||
@ -117,7 +124,12 @@ const MyApp = (({
|
||||
return () => { };
|
||||
}, []);
|
||||
|
||||
// Hotjar supprimé
|
||||
// Empêcher le rendu SSR du contenu qui dépend du navigateur
|
||||
if (!mounted) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
// Hotjar supprimé
|
||||
|
||||
return getLayout(
|
||||
<>
|
||||
@ -126,7 +138,7 @@ const MyApp = (({
|
||||
<GoogleTagManager gtmId="GTM-5GLJN86P" />
|
||||
</Component>
|
||||
}
|
||||
{isConnected && <Iframe />}
|
||||
{isConnected && <IframeNoSSR />}
|
||||
<Loader />
|
||||
</>
|
||||
);
|
||||
@ -147,7 +159,7 @@ MyApp.getInitialProps = async () => {
|
||||
idNotRedirectUri: publicRuntimeConfig.NEXT_PUBLIC_IDNOT_REDIRECT_URI,
|
||||
fcAuthorizeEndpoint: publicRuntimeConfig.NEXT_PUBLIC_FC_AUTHORIZE_ENDPOINT,
|
||||
fcClientId: publicRuntimeConfig.NEXT_PUBLIC_FC_CLIENT_ID,
|
||||
docaposteApiUrl: publicRuntimeConfig.NEXT_PUBLIC_DOCAPOST_API_URL,
|
||||
docaposteApiUrl: publicRuntimeConfig.NEXT_PUBLIC_DOCAPOSTE_API_URL,
|
||||
_4nkUrl: publicRuntimeConfig.NEXT_PUBLIC_4NK_URL,
|
||||
_4nkIframeUrl: publicRuntimeConfig.NEXT_PUBLIC_4NK_IFRAME_URL,
|
||||
apiUrl: publicRuntimeConfig.NEXT_PUBLIC_API_URL,
|
||||
|
@ -17,10 +17,23 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
|
||||
const [isIframeReady, setIsIframeReady] = useState(false);
|
||||
const [showIframe, setShowIframe] = useState(false);
|
||||
const [authSuccess, setAuthSuccess] = useState(false);
|
||||
const [iframeSrc, setIframeSrc] = useState<string | null>(null);
|
||||
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
|
||||
// Initialize iframe URL client-side only
|
||||
useEffect(() => {
|
||||
try {
|
||||
const url = IframeReference.getIframeUrl();
|
||||
setIframeSrc(url);
|
||||
} catch {
|
||||
setIframeSrc(null);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (!event.data || event.data.type === 'PassClientScriptReady') {
|
||||
return;
|
||||
@ -31,15 +44,20 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetOrigin = IframeReference.getTargetOrigin();
|
||||
if (!targetOrigin) {
|
||||
let targetOrigin: string | null = null;
|
||||
try {
|
||||
targetOrigin = IframeReference.getTargetOrigin();
|
||||
} catch {
|
||||
console.error('[AuthModal] handleMessage: targetOrigin not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!targetOrigin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Accepter seulement les messages de l'iframe (même origine)
|
||||
if (event.origin !== targetOrigin) {
|
||||
// Ignorer silencieusement les messages des DevTools et autres sources
|
||||
return;
|
||||
}
|
||||
|
||||
@ -58,7 +76,6 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
|
||||
setIsIframeReady(true);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'LINK_ACCEPTED': {
|
||||
setShowIframe(false);
|
||||
User.getInstance().setTokens(message.accessToken, message.refreshToken);
|
||||
@ -66,12 +83,10 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
|
||||
iframeRef.current.contentWindow!.postMessage({ type: 'GET_PAIRING_ID', accessToken: message.accessToken, messageId }, targetOrigin);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'PAIRING_CREATED': {
|
||||
console.log('[AuthModal] PAIRING_CREATED:', message);
|
||||
User.getInstance().setPairingId(message.userPairingId);
|
||||
setAuthSuccess(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setShowIframe(false);
|
||||
setIsIframeReady(false);
|
||||
@ -80,11 +95,9 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
|
||||
}, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'GET_PAIRING_ID': {
|
||||
User.getInstance().setPairingId(message.userPairingId);
|
||||
setAuthSuccess(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setShowIframe(false);
|
||||
setIsIframeReady(false);
|
||||
@ -93,28 +106,21 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
|
||||
}, 500);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ERROR': {
|
||||
console.error('[AuthModal] handleMessage: error', message);
|
||||
if (message.messageId.includes('GET_PAIRING_ID')) {
|
||||
// We are not paired yet
|
||||
if (message.messageId?.includes('GET_PAIRING_ID')) {
|
||||
const accessToken = User.getInstance().getAccessToken();
|
||||
if (accessToken) {
|
||||
// create a new pairing
|
||||
const messageId = `CREATE_PAIRING_${uuidv4()}`;
|
||||
iframeRef.current.contentWindow!.postMessage({ type: 'CREATE_PAIRING', accessToken, messageId }, targetOrigin);
|
||||
} else {
|
||||
// We don't have an access token
|
||||
// Shouldn't happen
|
||||
console.error('[AuthModal] handleMessage: error: we don\'t have an access token');
|
||||
console.error("[AuthModal] handleMessage: error: we don't have an access token");
|
||||
setShowIframe(false);
|
||||
setIsIframeReady(false);
|
||||
setAuthSuccess(false);
|
||||
onClose();
|
||||
}
|
||||
} else if (message.messageId.includes('CREATE_PAIRING')) {
|
||||
// Something went wrong while creating a pairing
|
||||
// show stopper for now
|
||||
} else if (message.messageId?.includes('CREATE_PAIRING')) {
|
||||
console.error('[AuthModal] CREATE_PAIRING error:', message.error);
|
||||
setShowIframe(false);
|
||||
setIsIframeReady(false);
|
||||
@ -125,8 +131,8 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener('message', handleMessage);
|
||||
|
||||
window.addEventListener('message', handleMessage);
|
||||
return () => {
|
||||
window.removeEventListener('message', handleMessage);
|
||||
};
|
||||
@ -178,17 +184,19 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
|
||||
alignItems: 'center',
|
||||
width: '100%'
|
||||
}}>
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
src={IframeReference.getIframeUrl()}
|
||||
style={{
|
||||
display: showIframe ? 'block' : 'none',
|
||||
width: '400px',
|
||||
height: '400px',
|
||||
border: 'none',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
/>
|
||||
{iframeSrc && (
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
src={iframeSrc}
|
||||
style={{
|
||||
display: showIframe ? 'block' : 'none',
|
||||
width: '400px',
|
||||
height: '400px',
|
||||
border: 'none',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useRef, useEffect, memo } from 'react';
|
||||
import { useRef, useEffect, memo, useState } from 'react';
|
||||
import IframeReference from './IframeReference';
|
||||
|
||||
interface IframeProps {
|
||||
@ -7,17 +7,30 @@ interface IframeProps {
|
||||
|
||||
function Iframe({ showIframe = false }: IframeProps) {
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
const [iframeSrc, setIframeSrc] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Client-side only: resolve iframe URL and set reference
|
||||
try {
|
||||
const url = IframeReference.getIframeUrl();
|
||||
setIframeSrc(url);
|
||||
} catch {
|
||||
setIframeSrc(null);
|
||||
}
|
||||
if (iframeRef.current) {
|
||||
IframeReference.setIframe(iframeRef.current);
|
||||
}
|
||||
}, [iframeRef.current]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (!iframeSrc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
src={IframeReference.getIframeUrl()}
|
||||
src={iframeSrc}
|
||||
style={{
|
||||
display: showIframe ? 'block' : 'none',
|
||||
width: '400px',
|
||||
|
@ -22,10 +22,12 @@ export default class MessageBus {
|
||||
}
|
||||
|
||||
public initMessageListener(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
window.addEventListener('message', this.handleMessage.bind(this));
|
||||
}
|
||||
|
||||
public destroyMessageListener(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
window.removeEventListener('message', this.handleMessage.bind(this));
|
||||
this.isListening = false; // Reset the flag when destroying listener
|
||||
}
|
||||
@ -1003,7 +1005,9 @@ export default class MessageBus {
|
||||
try {
|
||||
const targetOrigin = IframeReference.getTargetOrigin();
|
||||
const iframe = IframeReference.getIframe();
|
||||
iframe.contentWindow?.postMessage(message, targetOrigin);
|
||||
if (typeof window !== 'undefined') {
|
||||
iframe.contentWindow?.postMessage(message, targetOrigin);
|
||||
}
|
||||
this.messagesSent.add(message.messageId);
|
||||
} catch (error) {
|
||||
console.error('[MessageBus] sendMessage: error', error);
|
||||
|
Loading…
x
Reference in New Issue
Block a user