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