diff --git a/src/front/Stores/WindowStore.ts b/src/front/Stores/WindowStore.ts
index d15a1538..42abcabb 100644
--- a/src/front/Stores/WindowStore.ts
+++ b/src/front/Stores/WindowStore.ts
@@ -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);
}
}
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 4597a416..61464e44 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -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 ;
+ }
+
+ // Hotjar supprimé
return getLayout(
<>
@@ -126,7 +138,7 @@ const MyApp = (({
}
- {isConnected && }
+ {isConnected && }
>
);
@@ -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,
diff --git a/src/sdk/AuthModal.tsx b/src/sdk/AuthModal.tsx
index 4fb132a4..fecb6752 100644
--- a/src/sdk/AuthModal.tsx
+++ b/src/sdk/AuthModal.tsx
@@ -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(null);
const iframeRef = useRef(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%'
}}>
-
+ {iframeSrc && (
+
+ )}
)}
diff --git a/src/sdk/Iframe.tsx b/src/sdk/Iframe.tsx
index a2ac8afa..bdb1e0c5 100644
--- a/src/sdk/Iframe.tsx
+++ b/src/sdk/Iframe.tsx
@@ -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(null);
+ const [iframeSrc, setIframeSrc] = useState(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 (