Compare commits

..

3 Commits

Author SHA1 Message Date
fa41dda3a7 Deleted ProfileData 2025-10-08 11:26:59 +02:00
9a33f2ab0b Added new pairing functionnalities 2025-10-08 11:26:50 +02:00
d4ca148ac6 Now the ✕ button works 2025-10-08 11:23:50 +02:00
6 changed files with 140 additions and 244 deletions

View File

@ -351,39 +351,45 @@ export default function FoldersPage() {
}, []); }, []);
useEffect(() => { useEffect(() => {
if (isConnected) { const handleConnectionFlow = async () => {
if (!isConnected) return;
const userStore = UserStore.getInstance();
const messageBus = MessageBus.getInstance(iframeUrl); const messageBus = MessageBus.getInstance(iframeUrl);
messageBus.isReady().then(() => {
messageBus.getProcesses().then((processes: any) => { try {
setProcesses(processes); await messageBus.isReady();
});
}); let pairingId = userStore.getUserPairingId();
}
// 1⃣ Créer le pairing si non existant
if (!pairingId) {
console.log("🚀 No pairing found — creating new pairing...");
pairingId = await messageBus.createUserPairing();
console.log("✅ Pairing created:", pairingId);
userStore.pair(pairingId);
setUserPairingId(pairingId);
} else {
console.log("🔗 Already paired with ID:", pairingId);
}
// 2⃣ Charger les processes
const processes = await messageBus.getProcesses();
setProcesses(processes);
// 3⃣ Charger les myProcesses
const myProcesses = await messageBus.getMyProcesses();
setMyProcesses(myProcesses);
} catch (err) {
console.error("❌ Error during pairing or process loading:", err);
}
};
handleConnectionFlow();
}, [isConnected, iframeUrl]); }, [isConnected, iframeUrl]);
useEffect(() => {
if (isConnected && processes !== null) {
const messageBus = MessageBus.getInstance(iframeUrl);
messageBus.isReady().then(() => {
messageBus.getMyProcesses().then((res: string[]) => {
setMyProcesses(res);
})
});
}
}, [isConnected, processes]);
useEffect(() => {
if (isConnected && userPairingId === null) {
const messageBus = MessageBus.getInstance(iframeUrl);
messageBus.isReady().then(() => {
messageBus.getUserPairingId().then((userPairingId: string) => {
UserStore.getInstance().pair(userPairingId);
setUserPairingId(UserStore.getInstance().getUserPairingId());
})
});
}
}, [isConnected, userPairingId, processes]);
useEffect(() => { useEffect(() => {
// Simuler le chargement des dossiers // Simuler le chargement des dossiers
const loadFolders = () => { const loadFolders = () => {

View File

@ -282,39 +282,45 @@ export default function DashboardPage() {
}, []); }, []);
useEffect(() => { useEffect(() => {
if (isConnected) { const handleConnectionFlow = async () => {
if (!isConnected) return;
const userStore = UserStore.getInstance();
const messageBus = MessageBus.getInstance(iframeUrl); const messageBus = MessageBus.getInstance(iframeUrl);
messageBus.isReady().then(() => {
messageBus.getProcesses().then((processes: any) => { try {
setProcesses(processes); await messageBus.isReady();
});
}); let pairingId = userStore.getUserPairingId();
}
// 1⃣ Créer le pairing si non existant
if (!pairingId) {
console.log("🚀 No pairing found — creating new pairing...");
pairingId = await messageBus.createUserPairing();
console.log("✅ Pairing created:", pairingId);
userStore.pair(pairingId);
setUserPairingId(pairingId);
} else {
console.log("🔗 Already paired with ID:", pairingId);
}
// 2⃣ Charger les processes
const processes = await messageBus.getProcesses();
setProcesses(processes);
// 3⃣ Charger les myProcesses
const myProcesses = await messageBus.getMyProcesses();
setMyProcesses(myProcesses);
} catch (err) {
console.error("❌ Error during pairing or process loading:", err);
}
};
handleConnectionFlow();
}, [isConnected, iframeUrl]); }, [isConnected, iframeUrl]);
useEffect(() => {
if (isConnected && processes !== null) {
const messageBus = MessageBus.getInstance(iframeUrl);
messageBus.isReady().then(() => {
messageBus.getMyProcesses().then((res: string[]) => {
setMyProcesses(res);
})
});
}
}, [isConnected, processes]);
useEffect(() => {
if (isConnected && userPairingId === null) {
const messageBus = MessageBus.getInstance(iframeUrl);
messageBus.isReady().then(() => {
messageBus.getUserPairingId().then((userPairingId: string) => {
UserStore.getInstance().pair(userPairingId);
setUserPairingId(UserStore.getInstance().getUserPairingId());
})
});
}
}, [isConnected, userPairingId, processes]);
const handleOpenModal = (type: FolderType) => { const handleOpenModal = (type: FolderType) => {
setFolderType(type); setFolderType(type);
setIsModalOpen(true); setIsModalOpen(true);

View File

@ -20,7 +20,6 @@ export default function HomePage() {
setIsConnected(true); setIsConnected(true);
setShowAuthModal(false); setShowAuthModal(false);
router.push("/dashboard") router.push("/dashboard")
console.log('Auth Connect - Connexion établie, le useEffect se chargera de récupérer le userPairingId');
}, []); }, []);
const handleAuthClose = useCallback(() => { const handleAuthClose = useCallback(() => {

View File

@ -269,7 +269,17 @@ function ProcessesViewer({ processes, myProcesses, onProcessesUpdate }: Processe
</div> </div>
<button <button
className="text-sm text-gray-500 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-100" className="text-sm text-gray-500 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-100"
onClick={(e) => { e.stopPropagation(); setEditingField({ processId, stateId, key, value }); }} onClick={(e) => {
e.stopPropagation();
if (isEditing) {
// Fermer le mode édition
setEditingField(null);
setTempValue(null);
} else {
// Ouvrir le mode édition
setEditingField({ processId, stateId, key, value });
}
}}
> >
{isEditing ? '✕' : '🔄'} {isEditing ? '✕' : '🔄'}
</button> </button>

View File

@ -1,7 +1,6 @@
import IframeReference from './IframeReference'; import IframeReference from './IframeReference';
import EventBus from './EventBus'; import EventBus from './EventBus';
import UserStore from './UserStore'; import UserStore from './UserStore';
import { isProfileData, type ProfileCreated, type ProfileData } from './models/ProfileData';
import { isFolderData, type FolderCreated, type FolderData } from './models/FolderData'; import { isFolderData, type FolderCreated, type FolderData } from './models/FolderData';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import type { RoleDefinition } from './models/Roles'; import type { RoleDefinition } from './models/Roles';
@ -81,6 +80,48 @@ export default class MessageBus {
}); });
} }
public createUserPairing(): Promise<string> {
return new Promise<string>((resolve, reject) => {
this.checkToken().then(async () => {
const userStore = UserStore.getInstance();
const accessToken = userStore.getAccessToken();
if (!accessToken) {
return reject('No access token found');
}
const correlationId = uuidv4();
this.initMessageListener(correlationId);
// ✅ Success listener
const unsubscribeSuccess = EventBus.getInstance().on('PAIRING_CREATED', (responseId: string, pairingId: string) => {
if (responseId !== correlationId) return;
unsubscribeSuccess();
unsubscribeError();
this.destroyMessageListener();
resolve(pairingId);
});
// ❌ Error listener
const unsubscribeError = EventBus.getInstance().on('ERROR_CREATE_PAIRING', (responseId: string, error: string) => {
if (responseId !== correlationId) return;
unsubscribeError();
unsubscribeSuccess();
this.destroyMessageListener();
reject(error);
});
// 📨 Send CREATE_PAIRING message to iframe
this.sendMessage({
type: 'CREATE_PAIRING',
accessToken,
messageId: correlationId,
});
}).catch((err) => {
reject(`Failed to validate token before pairing: ${err}`);
});
});
}
public getUserPairingId(): Promise<string> { public getUserPairingId(): Promise<string> {
return new Promise<string>((resolve: (userPairingId: string) => void, reject: (error: string) => void) => { return new Promise<string>((resolve: (userPairingId: string) => void, reject: (error: string) => void) => {
this.checkToken().then(() => { this.checkToken().then(() => {
@ -337,61 +378,6 @@ export default class MessageBus {
}); });
} }
public createProfile(profileData: ProfileData, profilePrivateData: string[], roles: Record<string, RoleDefinition>): Promise<ProfileCreated> {
return new Promise<ProfileCreated>((resolve: (profileCreated: ProfileCreated) => void, reject: (error: string) => void) => {
this.checkToken().then(() => {
const userStore = UserStore.getInstance();
const accessToken = userStore.getAccessToken()!;
const correlationId = uuidv4();
this.initMessageListener(correlationId);
const unsubscribe = EventBus.getInstance().on('PROCESS_CREATED', (responseId: string, processCreated: any) => {
if (responseId !== correlationId) {
return;
}
unsubscribe();
this.destroyMessageListener();
// Return value must contain the data commited in the new process
const profileData = processCreated.processData;
if (!profileData || !isProfileData(profileData)) {
reject('Returned invalid profile data');
}
if (!processCreated.processId || typeof processCreated.processId !== 'string') {
console.error('Returned invalid process id');
reject('Returned invalid process id');
}
// TODO check that process is of type Process
const profileCreated: ProfileCreated = {
processId: processCreated.processId,
process: processCreated.process,
profileData
};
resolve(profileCreated);
});
const unsubscribeError = EventBus.getInstance().on('ERROR_PROCESS_CREATED', (responseId: string, error: string) => {
if (responseId !== correlationId) {
return;
}
unsubscribeError();
this.destroyMessageListener();
reject(error);
});
this.sendMessage({
type: 'CREATE_PROCESS',
processData: profileData,
privateFields: profilePrivateData,
roles,
accessToken
});
}).catch(console.error);
});
}
public createFolder(folderData: FolderData, folderPrivateData: string[], roles: Record<string, RoleDefinition>): Promise<FolderCreated> { public createFolder(folderData: FolderData, folderPrivateData: string[], roles: Record<string, RoleDefinition>): Promise<FolderCreated> {
return new Promise<FolderCreated>((resolve: (folderData: FolderCreated) => void, reject: (error: string) => void) => { return new Promise<FolderCreated>((resolve: (folderData: FolderCreated) => void, reject: (error: string) => void) => {
this.checkToken().then(() => { this.checkToken().then(() => {
@ -626,7 +612,7 @@ export default class MessageBus {
console.error('[MessageBus] sendMessage: iframe not found'); console.error('[MessageBus] sendMessage: iframe not found');
return; return;
} }
console.log('[MessageBus] sendMessage:', message, 'to', this.origin); console.log('[MessageBus] sendMessage:', message, 'to', this.origin);
iframe.contentWindow?.postMessage(message, this.origin); iframe.contentWindow?.postMessage(message, this.origin);
} }
@ -714,7 +700,7 @@ export default class MessageBus {
EventBus.getInstance().emit('PROCESSES_RETRIEVED', correlationId, message.processes); EventBus.getInstance().emit('PROCESSES_RETRIEVED', correlationId, message.processes);
break; break;
case 'GET_MY_PROCESSES': case 'GET_MY_PROCESSES':
if (this.errors[correlationId]) { if (this.errors[correlationId]) {
const error = this.errors[correlationId]; const error = this.errors[correlationId];
delete this.errors[correlationId]; delete this.errors[correlationId];
@ -756,7 +742,7 @@ export default class MessageBus {
EventBus.getInstance().emit('MESSAGE_RECEIVED', message); EventBus.getInstance().emit('MESSAGE_RECEIVED', message);
EventBus.getInstance().emit('UPDATE_NOTIFIED', correlationId); EventBus.getInstance().emit('UPDATE_NOTIFIED', correlationId);
break; break;
case 'STATE_VALIDATED': case 'STATE_VALIDATED':
if (this.errors[correlationId]) { if (this.errors[correlationId]) {
const error = this.errors[correlationId]; const error = this.errors[correlationId];
@ -789,7 +775,18 @@ export default class MessageBus {
EventBus.getInstance().emit('MESSAGE_RECEIVED', message); EventBus.getInstance().emit('MESSAGE_RECEIVED', message);
EventBus.getInstance().emit('PUBLIC_DATA_DECODED', correlationId, message.decodedData); EventBus.getInstance().emit('PUBLIC_DATA_DECODED', correlationId, message.decodedData);
break; break;
case 'PAIRING_CREATED':
if (this.errors[correlationId]) {
const error = this.errors[correlationId];
delete this.errors[correlationId];
EventBus.getInstance().emit('ERROR_PAIRING_CREATED', correlationId, error);
return;
}
EventBus.getInstance().emit('MESSAGE_RECEIVED', message);
EventBus.getInstance().emit('PAIRING_CREATED', correlationId, message.decodedData);
break;
case 'ERROR': case 'ERROR':
console.error('Error:', message); console.error('Error:', message);
this.errors[correlationId] = message.error; this.errors[correlationId] = message.error;

View File

@ -1,122 +0,0 @@
import { isFileBlob, type FileBlob } from "./Data";
import type { RoleDefinition } from "./Roles";
export interface ProfileData {
name: string;
surname: string;
email: string;
phone: string;
address: string;
postalCode: string;
city: string;
country: string;
idDocument: FileBlob | null;
idCertified: boolean;
}
export function isProfileData(data: any): data is ProfileData{
if (typeof data !== 'object' || data === null) return false;
const requiredStringFields = [
'name',
'surname',
'email',
'phone',
'address',
'postalCode',
'city',
'country',
];
for (const field of requiredStringFields) {
if (typeof data[field] !== 'string') return false;
}
const requiredBooleanFields = [
'idCertified',
];
for (const field of requiredBooleanFields) {
if (typeof data[field] !== 'boolean') return false;
}
const requiredFileFields = [
'idDocument',
];
for (const field of requiredFileFields) {
if (!isFileBlob(data[field]) && data[field] !== null) return false;
}
return true;
}
const emptyProfileData: ProfileData = {
name: '',
surname: '',
email: '',
phone: '',
address: '',
postalCode: '',
city: '',
country: '',
idDocument: null,
idCertified: false,
};
const profileDataFields: string[] = Object.keys(emptyProfileData);
const ProfilePublicFields: string[] = ['idCertified'];
export const ProfilePrivateFields = [
...profileDataFields.filter(key => !ProfilePublicFields.includes(key))
];
export interface ProfileCreated {
processId: string,
process: any, // Process
profileData: ProfileData,
}
export function setDefaultProfileRoles(ownerId: string[], validatorId: string): Record<string, RoleDefinition> {
return {
demiurge: {
members: [...ownerId, validatorId],
validation_rules: [],
storages: []
},
owner: {
members: ownerId,
validation_rules: [
{
quorum: 0.5,
fields: [...ProfilePrivateFields, 'roles'],
min_sig_member: 1,
},
],
storages: []
},
validator: {
members: [validatorId],
validation_rules: [
{
quorum: 0.5,
fields: ['idCertified', 'roles'],
min_sig_member: 1,
},
{
quorum: 0.0,
fields: [...profileDataFields],
min_sig_member: 0,
},
],
storages: []
},
apophis: {
members: ownerId,
validation_rules: [],
storages: []
}
}
};