feat: Optimisations majeures et nouvelles fonctionnalités

🚀 OPTIMISATIONS PERFORMANCE:
- Connexions WebSocket parallélisées (au lieu de séquentielles)
- Timeout handshake réduit de 10s à 3s
- Gestion d'erreur améliorée pour continuer même sans handshake
- Un seul relay suffit pour démarrer

🗑️ NETTOYAGE:
- Suppression du dossier /pages/process (entierement commenté)
- Redirection process -> account
- Suppression des références inutiles dans router.ts

🚨 EXPORT CRITIQUE D'AUTOVALIDATION:
- Nouveau bouton rouge 'Export Critique (Clé Privée)'
- Triple confirmation pour sécurité
- Export de la clé privée pour signature sans interaction
- Avertissements de sécurité multiples
- Fichier JSON avec instructions de sécurité

📊 RÉSULTATS:
- Initialisation plus rapide (connexions parallèles)
- Moins de blocages (timeout réduit)
- Fonctionnalité critique pour cas d'urgence
- Code plus propre (suppression du code mort)
This commit is contained in:
NicolasCantu 2025-10-22 17:38:57 +02:00
parent baad7c48bc
commit ef0f80e044
6 changed files with 3225 additions and 3724 deletions

View File

@ -356,6 +356,20 @@ export class DeviceManagementComponent extends HTMLElement {
flex: 1.2; flex: 1.2;
min-width: 150px; min-width: 150px;
} }
.import-export .btn-critical {
flex: 1.3;
min-width: 180px;
background: #dc3545;
color: white;
border: 2px solid #dc3545;
font-weight: bold;
}
.import-export .btn-critical:hover {
background: #c82333;
border-color: #c82333;
}
</style> </style>
<div class="device-management"> <div class="device-management">
@ -376,6 +390,7 @@ export class DeviceManagementComponent extends HTMLElement {
<div class="import-export"> <div class="import-export">
<button class="btn btn-secondary" id="importBtn">📥 Importer</button> <button class="btn btn-secondary" id="importBtn">📥 Importer</button>
<button class="btn btn-secondary" id="exportBtn">📤 Exporter</button> <button class="btn btn-secondary" id="exportBtn">📤 Exporter</button>
<button class="btn btn-critical" id="criticalExportBtn">🚨 Export Critique (Clé Privée)</button>
<button class="btn btn-danger" id="deleteAccountBtn">🗑 Supprimer le Compte</button> <button class="btn btn-danger" id="deleteAccountBtn">🗑 Supprimer le Compte</button>
</div> </div>
@ -446,6 +461,11 @@ export class DeviceManagementComponent extends HTMLElement {
this.exportAccount(); this.exportAccount();
}); });
// Critical export button
this.shadowRoot!.getElementById('criticalExportBtn')?.addEventListener('click', () => {
this.criticalExport();
});
// Delete account button // Delete account button
this.shadowRoot!.getElementById('deleteAccountBtn')?.addEventListener('click', () => { this.shadowRoot!.getElementById('deleteAccountBtn')?.addEventListener('click', () => {
this.deleteAccount(); this.deleteAccount();
@ -643,6 +663,66 @@ export class DeviceManagementComponent extends HTMLElement {
} }
} }
async criticalExport() {
// Triple confirmation for critical export
const confirm1 = confirm('🚨 EXPORT CRITIQUE: Cette action va exposer votre CLÉ PRIVÉE.\n\nCette clé permet de signer des transactions sans interaction sur le 2ème device.\n\nÊtes-vous sûr de vouloir continuer ?');
if (!confirm1) return;
const confirm2 = confirm('⚠️ SÉCURITÉ: Votre clé privée sera visible en clair.\n\nAssurez-vous que personne ne peut voir votre écran.\n\nContinuer ?');
if (!confirm2) return;
const confirm3 = confirm('🔐 DERNIÈRE CONFIRMATION: Cette clé privée donne un accès TOTAL à votre compte.\n\nTapez "EXPORTER" pour confirmer:');
if (confirm3 !== 'EXPORTER') {
alert('❌ Export critique annulé');
return;
}
try {
// Get the device's private key
const device = await this.service.getDeviceFromDatabase();
if (!device || !device.sp_wallet) {
throw new Error('Device ou clé privée non trouvée');
}
// Create critical export data
const criticalData = {
type: 'CRITICAL_EXPORT',
timestamp: new Date().toISOString(),
device_address: device.sp_wallet.address,
private_key: device.sp_wallet.private_key,
pairing_commitment: device.pairing_process_commitment,
warning: 'ATTENTION: Cette clé privée donne un accès total au compte. Gardez-la SECRÈTE et SÉCURISÉE.',
instructions: [
'1. Sauvegardez cette clé dans un endroit sûr',
'2. Ne la partagez JAMAIS avec qui que ce soit',
'3. Utilisez-la uniquement pour signer des transactions critiques',
'4. En cas de compromission, changez immédiatement votre compte'
]
};
// Create and download the file
const blob = new Blob([JSON.stringify(criticalData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `critical-export-${Date.now()}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
this.showStatus('🚨 Export critique généré - Clé privée exposée !', 'error');
// Show additional warning
setTimeout(() => {
alert('🚨 EXPORT CRITIQUE TERMINÉ\n\nVotre clé privée a été exportée.\n\n⚠ GARDEZ CE FICHIER SÉCURISÉ !');
}, 1000);
} catch (error) {
this.showStatus(`❌ Erreur lors de l'export critique: ${error}`, 'error');
}
}
async deleteAccount() { async deleteAccount() {
// First confirmation // First confirmation
if (!confirm('⚠️ Êtes-vous sûr de vouloir supprimer complètement votre compte ?\n\nCette action est IRRÉVERSIBLE et supprimera :\n• Tous vos processus\n• Toutes vos données\n• Votre wallet\n• Votre historique\n\nTapez "SUPPRIMER" pour confirmer.')) { if (!confirm('⚠️ Êtes-vous sûr de vouloir supprimer complètement votre compte ?\n\nCette action est IRRÉVERSIBLE et supprimera :\n• Tous vos processus\n• Toutes vos données\n• Votre wallet\n• Votre historique\n\nTapez "SUPPRIMER" pour confirmer.')) {

View File

@ -1,49 +0,0 @@
// import processHtml from './process.html?raw';
// import processScript from './process.ts?raw';
// import processCss from '../../4nk.css?raw';
// import { init } from './process';
// export class ProcessListComponent extends HTMLElement {
// _callback: any;
// constructor() {
// super();
// this.attachShadow({ mode: 'open' });
// }
// connectedCallback() {
// console.log('CALLBACK PROCESS LIST PAGE');
// this.render();
// setTimeout(() => {
// init();
// }, 500);
// }
// set callback(fn) {
// if (typeof fn === 'function') {
// this._callback = fn;
// } else {
// console.error('Callback is not a function');
// }
// }
// get callback() {
// return this._callback;
// }
// render() {
// if (this.shadowRoot)
// this.shadowRoot.innerHTML = `
// <style>
// ${processCss}
// </style>${processHtml}
// <script type="module">
// ${processScript}
// </scipt>
// `;
// }
// }
// if (!customElements.get('process-list-4nk-component')) {
// customElements.define('process-list-4nk-component', ProcessListComponent);
// }

View File

@ -1,19 +0,0 @@
<!-- <div class="title-container">
<h1>Process Selection</h1>
</div>
<div class="process-container">
<div class="process-card">
<div class="process-card-description">
<div class="input-container">
<select multiple data-multi-select-plugin id="autocomplete" placeholder="Filter processes..." class="select-field"></select>
<label for="autocomplete" class="input-label">Filter processes :</label>
<div class="selected-processes"></div>
</div>
<div class="process-card-content"></div>
</div>
<div class="process-card-action">
<a class="btn" onclick="goToProcessPage()">OK</a>
</div>
</div>
</div> -->

View File

@ -1,520 +0,0 @@
// import { addSubscription } from '../../utils/subscription.utils';
// import Services from '../../services/service';
// import { getCorrectDOM } from '~/utils/html.utils';
// import { Process } from 'pkg/sdk_client';
// import chatStyle from '../../../public/style/chat.css?inline';
// import { Database } from '../../services/database.service';
// // Initialize function, create initial tokens with itens that are already selected by the user
// export async function init() {
// const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
// const element = container.querySelector('select') as HTMLSelectElement;
// // Create div that wroaps all the elements inside (select, elements selected, search div) to put select inside
// const wrapper = document.createElement('div');
// if (wrapper) addSubscription(wrapper, 'click', clickOnWrapper);
// wrapper.classList.add('multi-select-component');
// wrapper.classList.add('input-field');
// // Create elements of search
// const search_div = document.createElement('div');
// search_div.classList.add('search-container');
// const input = document.createElement('input');
// input.classList.add('selected-input');
// input.setAttribute('autocomplete', 'off');
// input.setAttribute('tabindex', '0');
// if (input) {
// addSubscription(input, 'keyup', inputChange);
// addSubscription(input, 'keydown', deletePressed);
// addSubscription(input, 'click', openOptions);
// }
// const dropdown_icon = document.createElement('a');
// dropdown_icon.classList.add('dropdown-icon');
// if (dropdown_icon) addSubscription(dropdown_icon, 'click', clickDropdown);
// const autocomplete_list = document.createElement('ul');
// autocomplete_list.classList.add('autocomplete-list');
// search_div.appendChild(input);
// search_div.appendChild(autocomplete_list);
// search_div.appendChild(dropdown_icon);
// // set the wrapper as child (instead of the element)
// element.parentNode?.replaceChild(wrapper, element);
// // set element as child of wrapper
// wrapper.appendChild(element);
// wrapper.appendChild(search_div);
// addPlaceholder(wrapper);
// }
// function removePlaceholder(wrapper: HTMLElement) {
// const input_search = wrapper.querySelector('.selected-input');
// input_search?.removeAttribute('placeholder');
// }
// function addPlaceholder(wrapper: HTMLElement) {
// const input_search = wrapper.querySelector('.selected-input');
// const tokens = wrapper.querySelectorAll('.selected-wrapper');
// if (!tokens.length && !(document.activeElement === input_search)) input_search?.setAttribute('placeholder', '---------');
// }
// // Listener of user search
// function inputChange(e: Event) {
// const target = e.target as HTMLInputElement;
// const wrapper = target?.parentNode?.parentNode;
// const select = wrapper?.querySelector('select') as HTMLSelectElement;
// const dropdown = wrapper?.querySelector('.dropdown-icon');
// const input_val = target?.value;
// if (input_val) {
// dropdown?.classList.add('active');
// populateAutocompleteList(select, input_val.trim());
// } else {
// dropdown?.classList.remove('active');
// const event = new Event('click');
// dropdown?.dispatchEvent(event);
// }
// }
// // Listen for clicks on the wrapper, if click happens focus on the input
// function clickOnWrapper(e: Event) {
// const wrapper = e.target as HTMLElement;
// if (wrapper.tagName == 'DIV') {
// const input_search = wrapper.querySelector('.selected-input');
// const dropdown = wrapper.querySelector('.dropdown-icon');
// if (!dropdown?.classList.contains('active')) {
// const event = new Event('click');
// dropdown?.dispatchEvent(event);
// }
// (input_search as HTMLInputElement)?.focus();
// removePlaceholder(wrapper);
// }
// }
// function openOptions(e: Event) {
// const input_search = e.target as HTMLElement;
// const wrapper = input_search?.parentElement?.parentElement;
// const dropdown = wrapper?.querySelector('.dropdown-icon');
// if (!dropdown?.classList.contains('active')) {
// const event = new Event('click');
// dropdown?.dispatchEvent(event);
// }
// e.stopPropagation();
// }
// // Function that create a token inside of a wrapper with the given value
// function createToken(wrapper: HTMLElement, value: any) {
// const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
// const search = wrapper.querySelector('.search-container');
// const inputInderline = container.querySelector('.selected-processes');
// // Create token wrapper
// const token = document.createElement('div');
// token.classList.add('selected-wrapper');
// const token_span = document.createElement('span');
// token_span.classList.add('selected-label');
// token_span.innerText = value;
// const close = document.createElement('a');
// close.classList.add('selected-close');
// close.setAttribute('tabindex', '-1');
// close.setAttribute('data-option', value);
// close.setAttribute('data-hits', '0');
// close.innerText = 'x';
// if (close) addSubscription(close, 'click', removeToken);
// token.appendChild(token_span);
// token.appendChild(close);
// inputInderline?.appendChild(token);
// }
// // Listen for clicks in the dropdown option
// function clickDropdown(e: Event) {
// const dropdown = e.target as HTMLElement;
// const wrapper = dropdown?.parentNode?.parentNode;
// const input_search = wrapper?.querySelector('.selected-input') as HTMLInputElement;
// const select = wrapper?.querySelector('select') as HTMLSelectElement;
// dropdown.classList.toggle('active');
// if (dropdown.classList.contains('active')) {
// removePlaceholder(wrapper as HTMLElement);
// input_search?.focus();
// if (!input_search?.value) {
// populateAutocompleteList(select, '', true);
// } else {
// populateAutocompleteList(select, input_search.value);
// }
// } else {
// clearAutocompleteList(select);
// addPlaceholder(wrapper as HTMLElement);
// }
// }
// // Clears the results of the autocomplete list
// function clearAutocompleteList(select: HTMLSelectElement) {
// const wrapper = select.parentNode;
// const autocomplete_list = wrapper?.querySelector('.autocomplete-list');
// if (autocomplete_list) autocomplete_list.innerHTML = '';
// }
// async function populateAutocompleteList(select: HTMLSelectElement, query: string, dropdown = false) {
// const { autocomplete_options } = getOptions(select);
// let options_to_show = [];
// const service = await Services.getInstance();
// const mineArray: string[] = await service.getMyProcesses();
// const allProcesses = await service.getProcesses();
// const allArray: string[] = Object.keys(allProcesses).filter(x => !mineArray.includes(x));
// const wrapper = select.parentNode;
// const input_search = wrapper?.querySelector('.search-container');
// const autocomplete_list = wrapper?.querySelector('.autocomplete-list');
// if (autocomplete_list) autocomplete_list.innerHTML = '';
// const addProcessToList = (processId:string, isMine: boolean) => {
// const li = document.createElement('li');
// li.innerText = processId;
// li.setAttribute("data-value", processId);
// if (isMine) {
// li.classList.add("my-process");
// li.style.cssText = `color: var(--accent-color)`;
// }
// if (li) addSubscription(li, 'click', selectOption);
// autocomplete_list?.appendChild(li);
// };
// mineArray.forEach(processId => addProcessToList(processId, true));
// allArray.forEach(processId => addProcessToList(processId, false));
// if (mineArray.length === 0 && allArray.length === 0) {
// const li = document.createElement('li');
// li.classList.add('not-cursor');
// li.innerText = 'No options found';
// autocomplete_list?.appendChild(li);
// }
// }
// // Listener to autocomplete results when clicked set the selected property in the select option
// function selectOption(e: any) {
// console.log('🎯 Click event:', e);
// console.log('🎯 Target value:', e.target.dataset.value);
// const wrapper = e.target.parentNode.parentNode.parentNode;
// const select = wrapper.querySelector('select');
// const input_search = wrapper.querySelector('.selected-input');
// const option = wrapper.querySelector(`select option[value="${e.target.dataset.value}"]`);
// console.log('🎯 Selected option:', option);
// console.log('🎯 Process ID:', option?.getAttribute('data-process-id'));
// if (e.target.dataset.value.includes('messaging')) {
// const messagingNumber = parseInt(e.target.dataset.value.split(' ')[1]);
// const processId = select.getAttribute(`data-messaging-id-${messagingNumber}`);
// console.log('🚀 Dispatching newMessagingProcess event:', {
// processId,
// processName: `Messaging Process ${processId}`
// });
// // Dispatch l'événement avant la navigation
// document.dispatchEvent(new CustomEvent('newMessagingProcess', {
// detail: {
// processId: processId,
// processName: `Messaging Process ${processId}`
// }
// }));
// // Navigation vers le chat
// const navigateEvent = new CustomEvent('navigate', {
// detail: {
// page: 'chat',
// processId: processId || ''
// }
// });
// document.dispatchEvent(navigateEvent);
// return;
// }
// option.setAttribute('selected', '');
// createToken(wrapper, e.target.dataset.value);
// if (input_search.value) {
// input_search.value = '';
// }
// showSelectedProcess(e.target.dataset.value);
// input_search.focus();
// e.target.remove();
// const autocomplete_list = wrapper.querySelector('.autocomplete-list');
// if (!autocomplete_list.children.length) {
// const li = document.createElement('li');
// li.classList.add('not-cursor');
// li.innerText = 'No options found';
// autocomplete_list.appendChild(li);
// }
// const event = new Event('keyup');
// input_search.dispatchEvent(event);
// e.stopPropagation();
// }
// // function that returns a list with the autcomplete list of matches
// function autocomplete(query: string, options: any) {
// // No query passed, just return entire list
// if (!query) {
// return options;
// }
// let options_return = [];
// for (let i = 0; i < options.length; i++) {
// if (query.toLowerCase() === options[i].slice(0, query.length).toLowerCase()) {
// options_return.push(options[i]);
// }
// }
// return options_return;
// }
// // Returns the options that are selected by the user and the ones that are not
// function getOptions(select: HTMLSelectElement) {
// // Select all the options available
// const all_options = Array.from(select.querySelectorAll('option')).map((el) => el.value);
// // Get the options that are selected from the user
// const options_selected = Array.from(select.querySelectorAll('option:checked')).map((el: any) => el.value);
// // Create an autocomplete options array with the options that are not selected by the user
// const autocomplete_options: any[] = [];
// all_options.forEach((option) => {
// if (!options_selected.includes(option)) {
// autocomplete_options.push(option);
// }
// });
// autocomplete_options.sort();
// return {
// options_selected,
// autocomplete_options,
// };
// }
// // Listener for when the user wants to remove a given token.
// function removeToken(e: Event) {
// // Get the value to remove
// const target = e.target as HTMLSelectElement;
// const value_to_remove = target.dataset.option;
// const wrapper = target.parentNode?.parentNode?.parentNode;
// const input_search = wrapper?.querySelector('.selected-input');
// const dropdown = wrapper?.querySelector('.dropdown-icon');
// // Get the options in the select to be unselected
// const option_to_unselect = wrapper?.querySelector(`select option[value="${value_to_remove}"]`);
// option_to_unselect?.removeAttribute('selected');
// // Remove token attribute
// (target.parentNode as any)?.remove();
// dropdown?.classList.remove('active');
// const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
// const process = container.querySelector('#' + target.dataset.option);
// process?.remove();
// }
// // Listen for 2 sequence of hits on the delete key, if this happens delete the last token if exist
// function deletePressed(e: Event) {
// const input_search = e.target as HTMLInputElement;
// const wrapper = input_search?.parentNode?.parentNode;
// const key = (e as KeyboardEvent).keyCode || (e as KeyboardEvent).charCode;
// const tokens = wrapper?.querySelectorAll('.selected-wrapper');
// if (tokens?.length) {
// const last_token_x = tokens[tokens.length - 1].querySelector('a');
// let hits = +(last_token_x?.dataset?.hits || 0);
// if (key == 8 || key == 46) {
// if (!input_search.value) {
// if (hits > 1) {
// // Trigger delete event
// const event = new Event('click');
// last_token_x?.dispatchEvent(event);
// } else {
// if (last_token_x?.dataset.hits) last_token_x.dataset.hits = '2';
// }
// }
// } else {
// if (last_token_x?.dataset.hits) last_token_x.dataset.hits = '0';
// }
// }
// return true;
// }
// // Dismiss on outside click
// addSubscription(document, 'click', () => {
// // get select that has the options available
// const select = document.querySelectorAll('[data-multi-select-plugin]');
// for (let i = 0; i < select.length; i++) {
// if (event) {
// var isClickInside = select[i].parentElement?.parentElement?.contains(event.target as Node);
// if (!isClickInside) {
// const wrapper = select[i].parentElement?.parentElement;
// const dropdown = wrapper?.querySelector('.dropdown-icon');
// const autocomplete_list = wrapper?.querySelector('.autocomplete-list');
// //the click was outside the specifiedElement, do something
// dropdown?.classList.remove('active');
// if (autocomplete_list) autocomplete_list.innerHTML = '';
// addPlaceholder(wrapper as HTMLElement);
// }
// }
// }
// });
// async function showSelectedProcess(elem: MouseEvent) {
// const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
// if (elem) {
// const cardContent = container.querySelector('.process-card-content');
// const processes = await getProcesses();
// const process = processes.find((process: any) => process[1].title === elem);
// if (process) {
// const processDiv = document.createElement('div');
// processDiv.className = 'process';
// processDiv.id = process[0];
// const titleDiv = document.createElement('div');
// titleDiv.className = 'process-title';
// titleDiv.innerHTML = `${process[1].title} : ${process[1].description}`;
// processDiv.appendChild(titleDiv);
// for (const zone of process.zones) {
// const zoneElement = document.createElement('div');
// zoneElement.className = 'process-element';
// const zoneId = process[1].title + '-' + zone.id;
// zoneElement.setAttribute('zone-id', zoneId);
// zoneElement.setAttribute('process-title', process[1].title);
// zoneElement.setAttribute('process-id', `${process[0]}_${zone.id}`);
// zoneElement.innerHTML = `${zone.title}: ${zone.description}`;
// addSubscription(zoneElement, 'click', select);
// processDiv.appendChild(zoneElement);
// }
// if (cardContent) cardContent.appendChild(processDiv);
// }
// }
// }
// function select(event: Event) {
// const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
// const target = event.target as HTMLElement;
// const oldSelectedProcess = container.querySelector('.selected-process-zone');
// oldSelectedProcess?.classList.remove('selected-process-zone');
// if (target) {
// target.classList.add('selected-process-zone');
// }
// const name = target.getAttribute('zone-id');
// console.log('🚀 ~ select ~ name:', name);
// }
// function goToProcessPage() {
// const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
// const target = container.querySelector('.selected-process-zone');
// console.log('🚀 ~ goToProcessPage ~ event:', target);
// if (target) {
// const process = target?.getAttribute('process-id');
// console.log('=======================> going to process page', process);
// // navigate('process-element/' + process);
// document.querySelector('process-list-4nk-component')?.dispatchEvent(
// new CustomEvent('processSelected', {
// detail: {
// process: process,
// },
// }),
// );
// }
// }
// (window as any).goToProcessPage = goToProcessPage;
// async function createMessagingProcess(): Promise<void> {
// console.log('Creating messaging process');
// const service = await Services.getInstance();
// const otherMembers = [
// {
// sp_addresses: [
// "tsp1qqd7snxfh44am8f7a3x36znkh4v0dcagcgakfux488ghsg0tny7degq4gd9q4n4us0cyp82643f2p4jgcmtwknadqwl3waf9zrynl6n7lug5tg73a",
// "tsp1qqvd8pak9fyz55rxqj90wxazqzwupf2egderc96cn84h3l84z8an9vql85scudrmwvsnltfuy9ungg7pxnhys2ft5wnf2gyr3n4ukvezygswesjuc"
// ]
// },
// {
// sp_addresses: [
// "tsp1qqgl5vawdey6wnnn2sfydcejsr06uzwsjlfa6p6yr8u4mkqwezsnvyqlazuqmxhxd8crk5eq3wfvdwv4k3tn68mkj2nj72jj39d2ngauu4unfx0q7",
// "tsp1qqthmj56gj8vvkjzwhcmswftlrf6ye7ukpks2wra92jkehqzrvx7m2q570q5vv6zj6dnxvussx2h8arvrcfwz9sp5hpdzrfugmmzz90pmnganxk28"
// ]
// },
// {
// sp_addresses: [
// "tsp1qqwjtxr9jye7d40qxrsmd6h02egdwel6mfnujxzskgxvxphfya4e6qqjq4tsdmfdmtnmccz08ut24q8y58qqh4lwl3w8pvh86shlmavrt0u3smhv2",
// "tsp1qqwn7tf8q2jhmfh8757xze53vg2zc6x5u6f26h3wyty9mklvcy0wnvqhhr4zppm5uyyte4y86kljvh8r0tsmkmszqqwa3ecf2lxcs7q07d56p8sz5"
// ]
// }
// ];
// await service.checkConnections(otherMembers);
// const relayAddress = service.getAllRelays().pop();
// if (!relayAddress) {
// throw new Error('Empty relay address list');
// }
// const feeRate = 1;
// setTimeout(async () => {
// const createProcessReturn = await service.createMessagingProcess(otherMembers, relayAddress.spAddress, feeRate);
// const updatedProcess = createProcessReturn.updated_process.current_process;
// if (!updatedProcess) {
// console.error('Failed to retrieved new messaging process');
// return;
// }
// const processId = updatedProcess.states[0].commited_in;
// const stateId = updatedProcess.states[0].state_id;
// await service.handleApiReturn(createProcessReturn);
// const createPrdReturn = await service.createPrdUpdate(processId, stateId);
// await service.handleApiReturn(createPrdReturn);
// const approveChangeReturn = await service.approveChange(processId, stateId);
// await service.handleApiReturn(approveChangeReturn);
// }, 500)
// }
// async function getDescription(processId: string, process: Process): Promise<string | null> {
// const service = await Services.getInstance();
// // Get the `commited_in` value of the last state and remove it from the array
// const currentCommitedIn = process.states.pop()?.commited_in;
// if (currentCommitedIn === undefined) {
// return null; // No states available
// }
// // Find the last state where `commited_in` is different
// let lastDifferentState = process.states.findLast(
// state => state.commited_in !== currentCommitedIn
// );
// if (!lastDifferentState) {
// // It means that we only have one state that is not commited yet, that can happen with process we just created
// // let's assume that the right description is in the last concurrent state and not handle the (arguably rare) case where we have multiple concurrent states on a creation
// lastDifferentState = process.states.pop();
// }
// // Take the description out of the state, if any
// const description = lastDifferentState!.pcd_commitment['description'];
// if (description) {
// const userDiff = await service.getDiffByValue(description);
// if (userDiff) {
// console.log("Successfully retrieved userDiff:", userDiff);
// return userDiff.new_value;
// } else {
// console.log("Failed to retrieve a non-null userDiff.");
// }
// }
// return null;
// }

View File

@ -14,7 +14,6 @@ import { MerkleProofResult } from 'pkg/sdk_client';
const routes: { [key: string]: string } = { const routes: { [key: string]: string } = {
home: '/src/pages/home/home.html', home: '/src/pages/home/home.html',
process: '/src/pages/process/process.html',
'process-element': '/src/pages/process-element/process-element.html', 'process-element': '/src/pages/process-element/process-element.html',
account: '/src/pages/account/account.html', account: '/src/pages/account/account.html',
chat: '/src/pages/chat/chat.html', chat: '/src/pages/chat/chat.html',
@ -53,7 +52,7 @@ async function handleLocation(path: string) {
const accountComponent = document.createElement('login-4nk-component'); const accountComponent = document.createElement('login-4nk-component');
accountComponent.setAttribute('style', 'width: 100vw; height: 100vh; position: relative; grid-row: 2;'); accountComponent.setAttribute('style', 'width: 100vw; height: 100vh; position: relative; grid-row: 2;');
if (container) container.appendChild(accountComponent); if (container) container.appendChild(accountComponent);
} else if (path !== 'process') { } else {
const html = await fetch(routeHtml).then((data) => data.text()); const html = await fetch(routeHtml).then((data) => data.text());
content.innerHTML = html; content.innerHTML = html;
} }
@ -69,17 +68,9 @@ async function handleLocation(path: string) {
// modalService.injectValidationModal() // modalService.injectValidationModal()
switch (path) { switch (path) {
case 'process': case 'process':
// const { init } = await import('./pages/process/process'); // Process functionality removed - redirect to account
//const { ProcessListComponent } = await import('./pages/process/process-list-component'); console.warn('Process functionality has been removed, redirecting to account');
await navigate('account');
const container2 = document.querySelector('#containerId');
const accountComponent = document.createElement('process-list-4nk-component');
//if (!customElements.get('process-list-4nk-component')) {
//customElements.define('process-list-4nk-component', ProcessListComponent);
//}
accountComponent.setAttribute('style', 'height: 100vh; position: relative; grid-row: 2; grid-column: 4;');
if (container2) container2.appendChild(accountComponent);
break; break;
case 'process-element': case 'process-element':
@ -158,10 +149,9 @@ export async function init(): Promise<void> {
} }
// If we create a new device, we most probably don't have anything in db, but just in case // If we create a new device, we most probably don't have anything in db, but just in case
// Ces opérations doivent être séquentielles car elles dépendent les unes des autres
await services.restoreProcessesFromDB(); await services.restoreProcessesFromDB();
await services.restoreSecretsFromDB(); await services.restoreSecretsFromDB();
// We connect to all relays now
await services.connectAllRelays(); await services.connectAllRelays();
// We register all the event listeners if we run in an iframe // We register all the event listeners if we run in an iframe
@ -170,7 +160,7 @@ export async function init(): Promise<void> {
} }
if (services.isPaired()) { if (services.isPaired()) {
await navigate('process'); await navigate('account');
} else { } else {
await navigate('home'); await navigate('home');
} }

View File

@ -192,23 +192,42 @@ export default class Services {
* Waits for at least one handshake message before returning. * Waits for at least one handshake message before returning.
*/ */
public async connectAllRelays(): Promise<void> { public async connectAllRelays(): Promise<void> {
const connectedUrls: string[] = []; const relayUrls = Object.keys(this.relayAddresses);
console.log(`🚀 Connecting to ${relayUrls.length} relays in parallel...`);
// Connect to all relays // Connect to all relays in parallel
for (const wsurl of Object.keys(this.relayAddresses)) { const connectionPromises = relayUrls.map(async (wsurl) => {
try { try {
console.log(`Connecting to: ${wsurl}`); console.log(`🔗 Connecting to: ${wsurl}`);
await this.addWebsocketConnection(wsurl); await this.addWebsocketConnection(wsurl);
connectedUrls.push(wsurl); console.log(`✅ Successfully connected to: ${wsurl}`);
console.log(`Successfully connected to: ${wsurl}`); return wsurl;
} catch (error) { } catch (error) {
console.error(`Failed to connect to ${wsurl}:`, error); console.error(`❌ Failed to connect to ${wsurl}:`, error);
return null;
} }
} });
// Wait for all connections to complete (success or failure)
const results = await Promise.allSettled(connectionPromises);
const connectedUrls = results
.filter((result): result is PromiseFulfilledResult<string> =>
result.status === 'fulfilled' && result.value !== null)
.map(result => result.value);
console.log(`✅ Connected to ${connectedUrls.length}/${relayUrls.length} relays`);
// Wait for at least one handshake message if we have connections // Wait for at least one handshake message if we have connections
if (connectedUrls.length > 0) { if (connectedUrls.length > 0) {
await this.waitForHandshakeMessage(); try {
await this.waitForHandshakeMessage();
console.log(`✅ Handshake received from at least one relay`);
} catch (error) {
console.warn(`⚠️ No handshake received within timeout, but continuing with ${connectedUrls.length} connections`);
// Continue anyway - we have connections even without handshake
}
} else {
console.warn(`⚠️ No relay connections established`);
} }
} }
@ -1832,7 +1851,7 @@ export default class Services {
* This ensures that the relay addresses are fully populated and the member list is updated. * This ensures that the relay addresses are fully populated and the member list is updated.
* @returns A promise that resolves when at least one handshake message is received. * @returns A promise that resolves when at least one handshake message is received.
*/ */
private async waitForHandshakeMessage(timeoutMs: number = 10000): Promise<void> { private async waitForHandshakeMessage(timeoutMs: number = 3000): Promise<void> {
const startTime = Date.now(); const startTime = Date.now();
const pollInterval = 100; // Check every 100ms const pollInterval = 100; // Check every 100ms