Added the 4NK authentication

This commit is contained in:
Sadrinho27 2025-09-30 00:19:30 +02:00
parent 778abe903d
commit cc7414a441
9 changed files with 868 additions and 1037 deletions

View File

@ -25,8 +25,9 @@ import {
Home,
} from "lucide-react"
import { AuthModal } from "@/components/4nk/AuthModal"
import { MessageBus } from "@/lib/4nk/MessageBus"
import { UserStore } from "@/lib/4nk/UserStore"
import { Iframe } from "@/components/4nk/Iframe"
import MessageBus from "@/lib/4nk/MessageBus"
import UserStore from "@/lib/4nk/UserStore"
// DebugInfo supprimé
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
@ -40,7 +41,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
const router = useRouter()
const pathname = usePathname()
const iframeUrl = process.env.NEXT_PUBLIC_4NK_IFRAME_URL || "https://dev.4nk.io"
const iframeUrl = process.env.NEXT_PUBLIC_4NK_IFRAME_URL || "https://dev3.4nkweb.com"
const navigation = [
{ name: "Tableau de bord", href: "/dashboard", icon: LayoutDashboard },
@ -60,11 +61,10 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
const messageBus = MessageBus.getInstance(iframeUrl)
if (accessToken) {
// Vérifier si on est en mode mock
const mockMode = messageBus.isInMockMode()
setIsMockMode(mockMode)
// Mode normal (pas de mock)
setIsMockMode(false)
if (mockMode) {
if (false) {
console.log("🎭 Dashboard en mode mock")
setIsAuthenticated(true)
setUserInfo({
@ -117,7 +117,6 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
const messageBus = MessageBus.getInstance(iframeUrl)
userStore.disconnect()
messageBus.disableMockMode()
// Afficher un message de confirmation avec options
setShowLogoutConfirm(true)
@ -321,6 +320,14 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
</div>
)}
{/* Iframe 4NK - affichée seulement si connecté */}
{isAuthenticated && (
<Iframe
iframeUrl={process.env.NEXT_PUBLIC_4NK_IFRAME_URL || "https://dev3.4nkweb.com"}
showIframe={true}
/>
)}
{/* Debug info retiré */}
</div>
)

View File

@ -26,7 +26,7 @@ import {
HardDrive,
X,
} from "lucide-react"
import { MessageBus } from "@/lib/4nk/MessageBus"
import MessageBus from "@/lib/4nk/MessageBus"
import Link from "next/link"
export default function DashboardPage() {
@ -54,13 +54,12 @@ export default function DashboardPage() {
const [notifications, setNotifications] = useState<any[]>([])
useEffect(() => {
const iframeUrl = process.env.NEXT_PUBLIC_4NK_IFRAME_URL || "https://dev.4nk.io"
const iframeUrl = process.env.NEXT_PUBLIC_4NK_IFRAME_URL || "https://dev3.4nkweb.com"
const messageBus = MessageBus.getInstance(iframeUrl)
const mockMode = messageBus.isInMockMode()
setIsMockMode(mockMode)
setIsMockMode(false)
// Simuler le chargement des données
if (mockMode) {
if (false) {
setStats({
totalDocuments: 1247,
totalFolders: 89,

View File

@ -23,9 +23,9 @@ import {
EyeOff,
} from "lucide-react"
import { AuthModal } from "@/components/4nk/AuthModal"
import { MessageBus } from "@/lib/4nk/MessageBus"
import MessageBus from "@/lib/4nk/MessageBus"
import { MockService } from "@/lib/4nk/MockService"
import { UserStore } from "@/lib/4nk/UserStore"
import UserStore from "@/lib/4nk/UserStore"
export default function LoginPage() {
const [companyId, setCompanyId] = useState("")
@ -34,11 +34,17 @@ export default function LoginPage() {
const [showPairingSection, setShowPairingSection] = useState(false)
const [pairingWords, setPairingWords] = useState(["", "", "", ""])
const [pairingError, setPairingError] = useState("")
const [error, setError] = useState<string | null>(null)
const [pairingSuccess, setPairingSuccess] = useState(false)
const router = useRouter()
const [showPairingInput, setShowPairingInput] = useState(false)
const iframeUrl = process.env.NEXT_PUBLIC_4NK_IFRAME_URL || "https://dev.4nk.io"
const iframeUrl = process.env.NEXT_PUBLIC_4NK_IFRAME_URL || "https://dev3.4nkweb.com"
const handleLogin = () => {
setIsAuthModalOpen(true)
setError(null)
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
@ -58,8 +64,7 @@ export default function LoginPage() {
const mockService = MockService.getInstance()
const userStore = UserStore.getInstance()
// Activer le mode mock
messageBus.enableMockMode()
// Mode normal (pas de mock)
// Authentification mock
const authResult = await mockService.mockAuthentication(companyId)
@ -159,160 +164,57 @@ export default function LoginPage() {
<p className="text-gray-600">Gestion électronique de documents sécurisée</p>
</div>
{/* Navigation entre connexion et pairing */}
<div className="flex space-x-1 bg-gray-100 p-1 rounded-lg">
<button
onClick={() => setShowPairingSection(false)}
className={`flex-1 py-2 px-4 text-sm font-medium rounded-md transition-colors ${
!showPairingSection ? "bg-white text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"
}`}
>
<Building2 className="h-4 w-4 inline mr-2" />
Connexion
</button>
<button
onClick={() => setShowPairingSection(true)}
className={`flex-1 py-2 px-4 text-sm font-medium rounded-md transition-colors ${
showPairingSection ? "bg-white text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"
}`}
>
<Key className="h-4 w-4 inline mr-2" />
Pairing
</button>
</div>
{!showPairingSection ? (
/* Carte de connexion */
{/* Carte de connexion 4NK */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Building2 className="h-5 w-5 mr-2 text-blue-600" />
Identification d'entreprise
<CardTitle className="text-center">
<Shield className="h-8 w-8 mx-auto mb-4 text-blue-600" />
Connexion sécurisée 4NK
</CardTitle>
<CardDescription>Connectez-vous avec votre identifiant unique sécurisé par 4NK</CardDescription>
<CardDescription className="text-center">
Authentification cryptographique sans mot de passe
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="companyId">Votre identifiant unique</Label>
<Input
id="companyId"
type="text"
placeholder="Saisissez votre identifiant d'entreprise"
value={companyId}
onChange={(e) => setCompanyId(e.target.value)}
required
className="w-full"
/>
<CardContent className="space-y-6">
{/* Description de la connexion 4NK */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<h3 className="font-semibold text-blue-900 mb-2">🔐 Authentification 4NK</h3>
<ul className="text-sm text-blue-800 space-y-1">
<li> Aucun mot de passe requis</li>
<li> Identité cryptographique sécurisée</li>
<li> Chiffrement bout en bout</li>
<li> Protection par blockchain</li>
</ul>
</div>
{/* Info mode démonstration */}
<div className="bg-green-50 border border-green-200 rounded-lg p-3">
<div className="flex items-start space-x-2">
<TestTube className="h-4 w-4 text-green-600 mt-0.5 flex-shrink-0" />
<div className="text-xs text-green-700">
<p className="font-medium mb-1">Mode démonstration</p>
<p>
Utilisez l'identifiant <strong>"1234"</strong> pour accéder directement aux écrans de
démonstration avec des données simulées.
{/* Affichage des erreurs */}
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<p className="text-red-700 font-medium">Erreur de connexion :</p>
<p className="text-red-600 text-sm">{error}</p>
</div>
)}
{/* Bouton de connexion */}
<Button
onClick={handleLogin}
className="w-full"
size="lg"
disabled={isLoading}
>
<Shield className="h-5 w-5 mr-2" />
{isLoading ? "Connexion en cours..." : "Se connecter avec 4NK"}
</Button>
{/* Informations sur l'iframe */}
<div className="bg-gray-50 border border-gray-200 rounded-lg p-3">
<p className="text-xs text-gray-600 text-center">
<strong>URL d'authentification :</strong><br />
{iframeUrl}
</p>
</div>
</div>
</div>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? "Connexion en cours..." : "Se connecter"}
</Button>
</form>
</CardContent>
</Card>
) : (
/* Carte de pairing */
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Key className="h-5 w-5 mr-2 text-blue-600" />
Pairing d'appareil
</CardTitle>
<CardDescription>Ajoutez cet appareil à votre compte existant</CardDescription>
</CardHeader>
<CardContent>
{!pairingSuccess ? (
<form onSubmit={handlePairingSubmit} className="space-y-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label>Mots de pairing temporaires</Label>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => setShowPairingInput(!showPairingInput)}
className="text-blue-700 border-blue-300"
>
{showPairingInput ? <EyeOff className="h-4 w-4 mr-1" /> : <Eye className="h-4 w-4 mr-1" />}
{showPairingInput ? "Masquer" : "Afficher"}
</Button>
</div>
<p className="text-sm text-gray-600">Saisissez les 4 mots affichés sur votre autre appareil</p>
<div className="grid grid-cols-2 gap-2">
{pairingWords.map((word, index) => (
<Input
key={index}
type={showPairingInput ? "text" : "password"}
placeholder={`Mot ${index + 1}`}
value={word}
onChange={(e) => handlePairingWordChange(index, e.target.value)}
className="text-center font-mono select-none"
style={{ userSelect: "none", WebkitUserSelect: "none" }}
onContextMenu={(e) => e.preventDefault()}
onCopy={(e) => e.preventDefault()}
onCut={(e) => e.preventDefault()}
onPaste={(e) => e.preventDefault()}
autoComplete="off"
spellCheck={false}
required
/>
))}
</div>
</div>
{pairingError && (
<div className="bg-red-50 border border-red-200 rounded-lg p-3">
<div className="flex items-start space-x-2">
<AlertTriangle className="h-4 w-4 text-red-600 mt-0.5 flex-shrink-0" />
<p className="text-sm text-red-700">{pairingError}</p>
</div>
</div>
)}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
<div className="text-xs text-blue-700">
<p className="font-medium mb-1">Instructions :</p>
<ol className="space-y-1">
<li>1. Ouvrez DocV sur votre appareil principal</li>
<li>2. Allez dans Paramètres Sécurité</li>
<li>3. Cliquez sur "Ajouter un appareil"</li>
<li>4. Saisissez les 4 mots affichés ici</li>
</ol>
</div>
</div>
<Button type="submit" className="w-full">
<Key className="h-4 w-4 mr-2" />
Appairer cet appareil
</Button>
</form>
) : (
<div className="text-center py-6">
<CheckCircle className="h-12 w-12 mx-auto text-green-600 mb-4" />
<h3 className="text-lg font-semibold text-gray-900 mb-2">Pairing réussi !</h3>
<p className="text-gray-600 mb-4">Cet appareil a é ajouté à votre compte avec succès.</p>
<div className="animate-pulse text-blue-600">Redirection vers le dashboard...</div>
</div>
)}
</CardContent>
</Card>
)}
{/* Badges de sécurité */}
<div className="flex flex-wrap justify-center gap-2">

View File

@ -3,10 +3,9 @@
import { useState, useEffect, memo } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Shield, CheckCircle, Loader2, AlertCircle, RefreshCw } from "lucide-react"
import { Shield, CheckCircle, Loader2 } from "lucide-react"
import { Iframe } from "./Iframe"
import { MessageBus } from "@/lib/4nk/MessageBus"
import { IframeReference } from "@/lib/4nk/IframeReference"
import MessageBus from "@/lib/4nk/MessageBus"
interface AuthModalProps {
isOpen: boolean
@ -19,188 +18,38 @@ export const AuthModal = memo(function AuthModal({ isOpen, onConnect, onClose, i
const [isIframeReady, setIsIframeReady] = useState(false)
const [showIframe, setShowIframe] = useState(false)
const [authSuccess, setAuthSuccess] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [loadingStep, setLoadingStep] = useState("")
const [retryCount, setRetryCount] = useState(0)
const [iframeLoaded, setIframeLoaded] = useState(false)
const maxRetries = 3
useEffect(() => {
MessageBus.getInstance(iframeUrl).isReady().then(() => {
setIsIframeReady(true)
})
}, [iframeUrl])
useEffect(() => {
if (!isOpen) {
// Reset des états à la fermeture
setIsIframeReady(false)
setShowIframe(false)
setIsIframeReady(false)
setAuthSuccess(false)
setIsLoading(false)
setError(null)
setLoadingStep("")
setRetryCount(0)
setIframeLoaded(false)
return
}
}, [isOpen])
initAuth()
}, [isOpen, iframeUrl, retryCount])
const initAuth = async () => {
try {
setIsLoading(true)
setError(null)
setLoadingStep("Initialisation...")
console.log("🔗 Initialisation authentification 4NK avec:", iframeUrl)
console.log("🔄 Tentative:", retryCount + 1, "/", maxRetries + 1)
// Étape 1: Attendre que l'iframe soit disponible dans le DOM
setLoadingStep("Chargement de l'iframe...")
let attempts = 0
const maxAttempts = 40 // 20 secondes
while (attempts < maxAttempts) {
const iframe = IframeReference.getIframe()
if (iframe && iframe.contentWindow) {
console.log("✅ Iframe disponible après", attempts * 500, "ms")
break
}
await new Promise((resolve) => setTimeout(resolve, 500))
attempts++
}
if (attempts >= maxAttempts) {
throw new Error("Iframe 4NK non disponible dans le DOM après 20 secondes")
}
// Étape 2: Attendre que l'iframe soit complètement chargée
setLoadingStep("Attente du chargement complet...")
await waitForIframeLoad()
useEffect(() => {
if (isIframeReady && !showIframe) {
setShowIframe(true)
// Étape 3: Attendre le message LISTENING de l'iframe
setLoadingStep("Attente du signal LISTENING...")
const messageBus = MessageBus.getInstance(iframeUrl)
console.log("⏳ Attente du message LISTENING de l'iframe...")
await messageBus.isReady()
console.log("✅ Iframe prête et en écoute")
setIsIframeReady(true)
// Étape 4: Demander l'authentification (REQUEST_LINK)
setLoadingStep("Demande d'authentification...")
console.log("🔐 Envoi REQUEST_LINK...")
await messageBus.requestLink()
console.log("✅ LINK_ACCEPTED reçu, tokens stockés")
// Étape 5: Récupérer l'ID d'appairage
setLoadingStep("Récupération de l'identité...")
console.log("🆔 Récupération de l'ID d'appairage...")
await messageBus.getUserPairingId()
console.log("✅ ID d'appairage récupéré")
MessageBus.getInstance(iframeUrl).requestLink().then(() => {
setAuthSuccess(true)
// Délai avant de déclencher onConnect
setTimeout(() => {
onConnect()
}, 500)
} catch (err) {
console.error("❌ Authentication error:", err)
const errorMessage = err instanceof Error ? err.message : "Erreur d'authentification"
// Messages d'erreur plus spécifiques selon le protocole 4NK
if (errorMessage.includes("LINK_ACCEPTED")) {
setError("Erreur d'authentification : réponse inattendue du serveur 4NK")
} else if (errorMessage.includes("Tokens manquants")) {
setError("Erreur : les tokens d'authentification n'ont pas été reçus")
} else if (errorMessage.includes("LISTENING")) {
setError("L'iframe 4NK n'est pas en écoute. Vérifiez l'URL de l'iframe.")
} else if (errorMessage.includes("Timeout")) {
setError("Timeout : L'iframe 4NK ne répond pas. Vérifiez votre connexion.")
} else if (errorMessage.includes("origin")) {
setError("Erreur de configuration : les domaines ne correspondent pas")
} else {
setError(errorMessage)
}
setIsIframeReady(false)
setTimeout(() => onConnect(), 500)
}).catch((_error: string) => {
setShowIframe(false)
} finally {
setIsLoading(false)
setLoadingStep("")
}
}
setIsIframeReady(false)
setAuthSuccess(false)
const waitForIframeLoad = (): Promise<void> => {
return new Promise((resolve, reject) => {
const iframe = IframeReference.getIframe()
if (!iframe) {
reject(new Error("Iframe not found"))
return
}
// Si l'iframe est déjà chargée
if (iframeLoaded) {
resolve()
return
}
const timeout = setTimeout(() => {
cleanup()
reject(new Error("Timeout: L'iframe n'a pas fini de se charger"))
}, 30000) // 30 secondes pour le chargement
const cleanup = () => {
clearTimeout(timeout)
iframe.removeEventListener("load", onLoad)
iframe.removeEventListener("error", onError)
}
const onLoad = () => {
console.log("✅ Iframe loaded successfully")
setIframeLoaded(true)
cleanup()
// Attendre un peu plus pour que le contenu soit prêt
setTimeout(resolve, 2000)
}
const onError = () => {
console.error("❌ Iframe failed to load")
cleanup()
reject(new Error("Erreur de chargement de l'iframe"))
}
iframe.addEventListener("load", onLoad)
iframe.addEventListener("error", onError)
// Si l'iframe semble déjà chargée
if (iframe.contentDocument?.readyState === "complete") {
onLoad()
}
onClose()
})
}
const handleRetry = () => {
if (retryCount < maxRetries) {
setRetryCount((prev) => prev + 1)
setError(null)
} else {
setError("Nombre maximum de tentatives atteint. Veuillez vérifier votre connexion et réessayer plus tard.")
}
}
const handleForceRetry = () => {
setRetryCount(0)
setError(null)
setIframeLoaded(false)
// Forcer le rechargement de l'iframe
const iframe = IframeReference.getIframe()
if (iframe) {
iframe.src = iframe.src
}
}
}, [isIframeReady, showIframe, iframeUrl, onConnect, onClose])
if (!isOpen) return null
@ -215,107 +64,26 @@ export const AuthModal = memo(function AuthModal({ isOpen, onConnect, onClose, i
<CardDescription>Connexion sécurisée avec votre identité cryptographique</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<div className="flex items-start space-x-2">
<AlertCircle className="h-5 w-5 text-red-600 mt-0.5 flex-shrink-0" />
<div className="flex-1">
<p className="text-red-700 text-sm font-medium mb-2">Erreur de connexion</p>
<p className="text-red-600 text-xs mb-3">{error}</p>
<div className="flex flex-col gap-2">
{retryCount < maxRetries && (
<Button
variant="outline"
size="sm"
onClick={handleRetry}
className="bg-transparent text-red-700 border-red-300 hover:bg-red-50"
>
<RefreshCw className="h-4 w-4 mr-2" />
Réessayer ({retryCount + 1}/{maxRetries + 1})
</Button>
)}
<Button
variant="outline"
size="sm"
onClick={handleForceRetry}
className="bg-transparent text-red-700 border-red-300 hover:bg-red-50"
>
<RefreshCw className="h-4 w-4 mr-2" />
Forcer le rechargement
</Button>
</div>
</div>
</div>
{!isIframeReady && (
<div className="flex flex-col items-center justify-center h-96 gap-4">
<Loader2 className="h-10 w-10 animate-spin text-blue-600" />
<div className="font-semibold text-lg">Chargement de l'authentification...</div>
</div>
)}
{isLoading && !authSuccess && (
<div className="text-center py-8">
<Loader2 className="h-8 w-8 animate-spin mx-auto mb-4 text-blue-600" />
<p className="text-gray-600 font-medium">{loadingStep}</p>
{loadingStep && <p className="text-gray-500 text-sm mt-2">Protocole 4NK en cours...</p>}
{/* Barre de progression visuelle */}
<div className="mt-4 w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-500"
style={{
width:
loadingStep === "Initialisation..."
? "15%"
: loadingStep === "Chargement de l'iframe..."
? "30%"
: loadingStep === "Attente du chargement complet..."
? "45%"
: loadingStep === "Attente du signal LISTENING..."
? "60%"
: loadingStep === "Demande d'authentification..."
? "80%"
: loadingStep === "Récupération de l'identité..."
? "95%"
: "0%",
}}
{authSuccess ? (
<div className="flex flex-col items-center justify-center h-96 gap-5">
<CheckCircle className="h-12 w-12 text-green-600" />
<div className="font-semibold text-lg text-green-700">
Authentification réussie !
</div>
</div>
) : (
<div className="flex justify-center items-center w-full">
<Iframe
iframeUrl={iframeUrl}
showIframe={showIframe}
/>
</div>
</div>
)}
{authSuccess && (
<div className="text-center py-8">
<CheckCircle className="h-12 w-12 text-green-600 mx-auto mb-4" />
<p className="text-green-700 font-semibold">Authentification réussie !</p>
<p className="text-gray-600 text-sm mt-2">Tokens stockés Redirection en cours...</p>
</div>
)}
{showIframe && !authSuccess && !error && (
<div className="border rounded-lg overflow-hidden">
<div className="bg-blue-50 p-2 text-center">
<p className="text-blue-700 text-sm">Interface d'authentification 4NK</p>
<p className="text-blue-600 text-xs">En attente de LISTENING REQUEST_LINK LINK_ACCEPTED</p>
</div>
<Iframe iframeUrl={iframeUrl} showIframe={true} />
</div>
)}
{/* Informations de debug */}
{(error || isLoading) && (
<div className="bg-gray-50 p-3 rounded-lg text-xs">
<p>
<strong>URL:</strong> {iframeUrl}
</p>
<p>
<strong>Tentative:</strong> {retryCount + 1}/{maxRetries + 1}
</p>
<p>
<strong>Iframe chargée:</strong> {iframeLoaded ? "Oui" : "Non"}
</p>
<p>
<strong>Protocole:</strong> LISTENING REQUEST_LINK LINK_ACCEPTED
</p>
</div>
)}
<div className="text-center">

View File

@ -1,7 +1,7 @@
"use client"
import { useRef, useEffect, memo } from "react"
import { IframeReference } from "@/lib/4nk/IframeReference"
import IframeReference from "@/lib/4nk/IframeReference"
interface IframeProps {
iframeUrl: string
@ -19,19 +19,19 @@ export const Iframe = memo(function Iframe({ iframeUrl, showIframe = false }: If
return () => {
IframeReference.setIframe(null)
}
}, [])
}, [iframeRef.current])
return (
<iframe
ref={iframeRef}
src={iframeUrl}
style={{
width: showIframe ? "100%" : "0",
height: showIframe ? "400px" : "0",
border: "none",
display: showIframe ? "block" : "none",
display: showIframe ? 'block' : 'none',
width: '400px',
height: '400px',
border: 'none',
overflow: 'hidden'
}}
sandbox="allow-scripts allow-same-origin allow-forms"
title="4NK Authentication"
/>
)

View File

@ -1,47 +1,33 @@
/**
* EventBus - Bus d'événements intra-onglet pour la communication interne
* Pattern Singleton avec pub/sub
*/
export class EventBus {
private static instance: EventBus | null = null
private listeners: Map<string, Array<(...args: any[]) => void>> = new Map()
export default class EventBus {
private static instance: EventBus;
private listeners: Record<string, Array<(...args: any[]) => void>> = {};
private constructor() {}
private constructor() { }
static getInstance(): EventBus {
public static getInstance(): EventBus {
if (!EventBus.instance) {
EventBus.instance = new EventBus()
EventBus.instance = new EventBus();
}
return EventBus.instance
return EventBus.instance;
}
on(event: string, callback: (...args: any[]) => void): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, [])
public on(event: string, callback: (...args: any[]) => void): () => void {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
const eventListeners = this.listeners.get(event)!
eventListeners.push(callback)
// Retourne une fonction d'unsubscribe
this.listeners[event].push(callback);
return () => {
const index = eventListeners.indexOf(callback)
if (index > -1) {
eventListeners.splice(index, 1)
}
if (this.listeners[event]) {
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
}
};
}
emit(event: string, ...args: any[]): void {
const eventListeners = this.listeners.get(event)
if (eventListeners) {
eventListeners.forEach((callback) => {
try {
callback(...args)
} catch (error) {
console.error(`Error in event listener for ${event}:`, error)
}
})
public emit(event: string, ...args: any[]): void {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => {
callback(...args);
});
}
}
}

View File

@ -1,15 +1,13 @@
/**
* IframeReference - Référence globale pour l'iframe 4NK
* Permet aux autres services d'accéder à l'iframe pour postMessage
*/
export class IframeReference {
private static iframe: HTMLIFrameElement | null = null
export default class IframeReference {
private static iframe: HTMLIFrameElement | null = null;
static setIframe(iframe: HTMLIFrameElement | null): void {
IframeReference.iframe = iframe
private constructor() { }
public static setIframe(iframe: HTMLIFrameElement | null): void {
this.iframe = iframe;
}
static getIframe(): HTMLIFrameElement | null {
return IframeReference.iframe
public static getIframe(): HTMLIFrameElement | null {
return this.iframe;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,59 +1,43 @@
/**
* UserStore - Singleton pour la gestion des tokens et identifiants utilisateur
* Stockage en sessionStorage selon les spécifications 4NK
*/
export class UserStore {
private static instance: UserStore | null = null
export default class UserStore {
private static instance: UserStore;
private constructor() {}
private constructor() { }
static getInstance(): UserStore {
public static getInstance(): UserStore {
if (!UserStore.instance) {
UserStore.instance = new UserStore()
UserStore.instance = new UserStore();
}
return UserStore.instance
return UserStore.instance;
}
connect(accessToken: string, refreshToken: string): void {
if (typeof window !== "undefined") {
sessionStorage.setItem("4nk_access_token", accessToken)
sessionStorage.setItem("4nk_refresh_token", refreshToken)
}
public connect(accessToken: string, refreshToken: string): void {
sessionStorage.setItem('accessToken', accessToken);
sessionStorage.setItem('refreshToken', refreshToken);
}
isConnected(): boolean {
if (typeof window === "undefined") return false
const accessToken = sessionStorage.getItem("4nk_access_token")
const refreshToken = sessionStorage.getItem("4nk_refresh_token")
return !!(accessToken && refreshToken)
public isConnected(): boolean {
return sessionStorage.getItem('accessToken') !== null && sessionStorage.getItem('refreshToken') !== null;
}
disconnect(): void {
if (typeof window !== "undefined") {
sessionStorage.removeItem("4nk_access_token")
sessionStorage.removeItem("4nk_refresh_token")
sessionStorage.removeItem("4nk_user_pairing_id")
}
public disconnect(): void {
sessionStorage.removeItem('accessToken');
sessionStorage.removeItem('refreshToken');
sessionStorage.removeItem('userPairingId');
}
getAccessToken(): string | null {
if (typeof window === "undefined") return null
return sessionStorage.getItem("4nk_access_token")
public getAccessToken(): string | null {
return sessionStorage.getItem('accessToken');
}
getRefreshToken(): string | null {
if (typeof window === "undefined") return null
return sessionStorage.getItem("4nk_refresh_token")
public getRefreshToken(): string | null {
return sessionStorage.getItem('refreshToken');
}
pair(userPairingId: string): void {
if (typeof window !== "undefined") {
sessionStorage.setItem("4nk_user_pairing_id", userPairingId)
}
public pair(userPairingId: string): void {
sessionStorage.setItem('userPairingId', userPairingId);
}
getUserPairingId(): string | null {
if (typeof window === "undefined") return null
return sessionStorage.getItem("4nk_user_pairing_id")
public getUserPairingId(): string | null {
return sessionStorage.getItem('userPairingId');
}
}