From 7bf0fb2b4f2988f79455c47978420a04c5b45d1b Mon Sep 17 00:00:00 2001 From: NicolasCantu Date: Fri, 14 Feb 2025 16:25:27 +0100 Subject: [PATCH] WIP (to be recommited smaller later) --- src/pages/chat/chat.ts | 501 ++++++++++++++------------------ src/services/service.ts | 378 +++++++++--------------- src/services/storage.service.ts | 35 ++- 3 files changed, 378 insertions(+), 536 deletions(-) diff --git a/src/pages/chat/chat.ts b/src/pages/chat/chat.ts index 373eb10..59b29f0 100755 --- a/src/pages/chat/chat.ts +++ b/src/pages/chat/chat.ts @@ -286,7 +286,10 @@ class ChatElement extends HTMLElement { try { const service = await Services.getInstance(); const myAddresses = await service.getMemberFromDevice(); - if (!myAddresses) throw new Error('No paired member found'); + if (!myAddresses) { + console.error('No paired member found'); + return; + } const timestamp = Date.now(); const message = { @@ -307,37 +310,24 @@ class ChatElement extends HTMLElement { if (!process) { console.error('Failed to retrieve process from DB'); return; - } else { - console.log(process); } - const processTip = process.states[process.states.length - 1].commited_in; - console.log(processTip); + // For a dm process, there are only 2 attributes, description will stay the same, message is the new message + // We don't need to get previous values for now, so let's just skip it + let newState = { + message: message, + description: 'dm' + }; - // We take the last commited state and copy all values - const lastCommitedState = process.states.findLast( - state => state.commited_in !== processTip - ); - - if (!lastCommitedState) { - console.error('Failed to find last commited state'); - return; - } else { - console.log(lastCommitedState); - } - - let newState = {}; - // fetch all the values from diff - for (const [attribute, hash] of Object.entries(lastCommitedState.pcd_commitment)) { - const value = await service.getDiffByValue(hash); - newState[attribute] = value.new_value; - } - - newState.message = message; - - console.log(`Creating state ${newState}`); // Now we create a new state for the dm process - const apiReturn = await service.updateProcess(this.processId, newState); + let apiReturn; + try { + console.log(process); + apiReturn = await service.updateProcess(process, newState, null); + } catch (e) { + console.error('Failed to update process:', e); + return; + } const updatedProcess = apiReturn.updated_process.current_process; const newStateId = updatedProcess.states[updatedProcess.states.length - 2 ].state_id; // We take the last concurrent state, just before the tip console.log(`newStateId: ${newStateId}`); @@ -383,7 +373,7 @@ class ChatElement extends HTMLElement { const processRoles = this.processRoles; const selectedMember = this.selectedMember; for (const child of children) { - const roles = await this.getRoles(JSON.parse(child)); + const roles = await service.getRoles(JSON.parse(child)); // Check that we and the other members are in the role if (!service.isChildRole(processRoles, roles)) { console.error('Child process roles are not a subset of parent') @@ -478,27 +468,31 @@ class ChatElement extends HTMLElement { } private async lookForDmProcess(): Promise { - // Filter processes for the children of current process const service = await Services.getInstance(); - const processes = await service.getProcesses(); - const memberAddresses = await service.getAddressesForMemberId(this.selectedMember); + const processes = await service.getMyProcesses(); + console.log(processes); + const recipientAddresses = await service.getAddressesForMemberId(this.selectedMember).sp_addresses; + console.log(recipientAddresses); - const recipientAddresses = memberAddresses.sp_addresses; - for (const [processId, process] of Object.entries(processes)) { - const description = await service.getDescription(processId, process); - if (description !== "dm") { - continue; + for (const processId of processes) { + try { + const process = await service.getProcess(processId); + console.log(process); + const state = process.states[0]; // We assume that description never change and that we are part of the process from the beginning + const description = await service.decryptAttribute(state, 'description'); + console.log(description); + if (!description || description !== "dm") { + continue; + } + const roles = await service.getRoles(process); + if (!service.rolesContainsMember(roles, recipientAddresses)) { + console.error('Member is not part of the process'); + continue; + } + return processId; + } catch (e) { + console.error(e); } - const roles = await this.getRoles(process); - if (!service.rolesContainsMember(roles, recipientAddresses)) { - console.error('Member is not part of the process'); - continue; - } - if (!service.rolesContainsUs(roles)) { - console.error('We\'re not part of child process'); - continue; - } - return processId; } return null; @@ -911,43 +905,6 @@ class ChatElement extends HTMLElement { roleElement.appendChild(memberList); } - async getRoles(process: Process): Promise { - 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(); - } - - if (!lastDifferentState || !lastDifferentState.pcd_commitment) { - return null; - } - - // Take the roles out of the state - const roles = lastDifferentState!.pcd_commitment['roles']; - if (roles) { - const userDiff = await service.getDiffByValue(roles); - if (userDiff) { - console.log("Successfully retrieved userDiff:", userDiff); - return userDiff.new_value; - } - } - - return null; - } private async switchTab(tabType: string, tabs: NodeListOf) { // Mettre à jour les classes des onglets @@ -980,200 +937,172 @@ class ChatElement extends HTMLElement { } } + //load all processes from the service private async loadAllProcesses(processSet: Set) { console.log('🎯 Loading all processes'); - console.log("Je suis le processSet dans loadAllProcesses :", processSet); - this.closeSignature(); + const allProcesses = await this.getProcesses(); - const dbRequest = indexedDB.open('4nk'); - - return new Promise((resolve, reject) => { - dbRequest.onerror = (event) => { - console.error('❌ Error opening database:', dbRequest.error); - reject(dbRequest.error); - }; - dbRequest.onsuccess = async (event) => { - const db = dbRequest.result; - const transaction = db.transaction(['processes'], 'readonly'); - const store = transaction.objectStore('processes'); - - const request = store.getAll(); - - request.onsuccess = async () => { - const processResult = request.result; - console.log('🎯 Processed result:', processResult); - - // Afficher les processus dans le container #group-list - const groupList = this.shadowRoot?.querySelector('#group-list'); - if (!groupList) { - console.warn('⚠️ Group list element not found'); - return; - } + // Afficher les processus dans le container #group-list + const groupList = this.shadowRoot?.querySelector('#group-list'); + if (!groupList) { + console.warn('⚠️ Group list element not found'); + return; + } - groupList.innerHTML = ''; + groupList.innerHTML = ''; - const tabContent = document.createElement('div'); - tabContent.className = 'tabs'; - tabContent.innerHTML = ` - - - `; - groupList.appendChild(tabContent); + const tabContent = document.createElement('div'); + tabContent.className = 'tabs'; + tabContent.innerHTML = ` + + + `; + groupList.appendChild(tabContent); - // Ajouter les event listeners - const tabs = tabContent.querySelectorAll('.tab'); - tabs.forEach(tab => { - tab.addEventListener('click', () => { - const tabType = tab.getAttribute('data-tab'); - if (tabType) { - this.switchTab(tabType, tabs); - } - }); - }); - - //trier les processus : ceux de l'utilisateur en premier - processResult.sort((a, b) => { - const aInSet = this.userProcessSet.has(a.states[0].commited_in); - const bInSet = this.userProcessSet.has(b.states[0].commited_in); - return bInSet ? 1 : aInSet ? -1 : 0; - }); - - for (const process of processResult) { - const li = document.createElement('li'); - li.className = 'group-list-item'; - const oneProcess = process.states[0].commited_in; - let roles; - try { - roles = await this.getRoles(process); - if (!roles) { - roles = await process.states[0].encrypted_pcd.roles; - } - } catch (e) { - // console.error('Failed to get roles for process:', process); - continue; - } - - // Si le processus est dans notre Set, ajouter la classe my-process - if (this.userProcessSet && this.userProcessSet.has(oneProcess)) { - li.style.cssText = ` - background-color: var(--accent-color); - transition: background-color 0.3s ease; - cursor: pointer; - `; - li.onmouseover = () => { - li.style.backgroundColor = 'var(--accent-color-hover)'; - }; - li.onmouseout = () => { - li.style.backgroundColor = 'var(--accent-color)'; - }; - console.log("✅ Processus trouvé dans le set:", oneProcess); - } - - li.setAttribute('data-process-id', oneProcess); - //----MANAGE THE CLICK ON PROCESS ---- - li.onclick = async (event) => { - event.stopPropagation(); - console.log("CLICKED ON PROCESS:", oneProcess); - //viser le h1 de signature-header - const signatureHeader = this.shadowRoot?.querySelector('.signature-header h1'); - if (signatureHeader) { - const emoji = await addressToEmoji(oneProcess); - signatureHeader.textContent = `Signature of ${emoji}`; - } - this.openSignature(); - - //afficher les roles dans chaque processus - console.log('🎯 Roles de signature:', roles); - await this.loadAllRolesAndMembersInSignature(roles); - //----MANAGE THE CLICK ON NEW REQUEST ---- - await this.newRequest(oneProcess); - }; - groupList.appendChild(li); - - const container = document.createElement('div'); - container.className = 'group-item-container'; - - const nameSpan = document.createElement('span'); - nameSpan.textContent = `Process : `; - nameSpan.className = 'process-name'; - - container.appendChild(nameSpan); - - addressToEmoji(oneProcess).then(emojis => { - const emojiSpan = document.createElement('span'); - emojiSpan.className = 'process-emoji'; - emojiSpan.textContent = emojis; - container.appendChild(emojiSpan); - }); - - li.appendChild(container); - - // afficher les roles dans chaque processus - - //console.log('🎯 Roles:', roles); - const roleList = document.createElement('ul'); - roleList.className = 'role-list'; - (roleList as HTMLElement).style.display = 'none'; - - // Traiter chaque rôle - Object.entries(roles).forEach(([roleName, roleData]: [string, any]) => { - const roleItem = document.createElement('li'); - roleItem.className = 'role-item'; - - const roleContainer = document.createElement('div'); - roleContainer.className = 'role-item-container'; - - const roleNameSpan = document.createElement('span'); - roleNameSpan.className = 'role-name'; - roleNameSpan.textContent = roleName; - - // Filtrer les membres dupliqués ici, avant de les passer à toggleMembers - const uniqueMembers = new Map(); - roleData.members?.forEach((member: any) => { - const spAddress = member.sp_addresses?.[0]; - if (spAddress && !uniqueMembers.has(spAddress)) { - uniqueMembers.set(spAddress, member); - } - }); - - // Créer un nouveau roleData avec les membres uniques - const filteredRoleData = { - ...roleData, - members: Array.from(uniqueMembers.values()) - }; - - roleContainer.addEventListener('click', async (event) => { - console.log("CLICKED ON ROLE:", roleName); - event.stopPropagation(); - await this.toggleMembers(filteredRoleData, roleItem); - }); - - roleContainer.appendChild(roleNameSpan); - roleItem.appendChild(roleContainer); - roleList.appendChild(roleItem); - }); - - li.appendChild(roleList); - groupList.appendChild(li); - - container.addEventListener('click', (event) => { - event.stopPropagation(); - container.classList.toggle('expanded'); - roleList.style.display = container.classList.contains('expanded') ? 'block' : 'none'; - }); - } - - resolve(processResult); - }; - - request.onerror = () => { - console.error('❌ Error getting processes:', request.error); - reject(request.error); - }; - }; + // Ajouter les event listeners + const tabs = tabContent.querySelectorAll('.tab'); + tabs.forEach(tab => { + tab.addEventListener('click', () => { + const tabType = tab.getAttribute('data-tab'); + if (tabType) { + this.switchTab(tabType, tabs); + } + }); }); + + //trier les processus : ceux de l'utilisateur en premier + allProcesses.sort((a, b) => { + const aInSet = this.userProcessSet.has(a.value.states[0].commited_in); + const bInSet = this.userProcessSet.has(b.value.states[0].commited_in); + return bInSet ? 1 : aInSet ? -1 : 0; + }); + + for (const process of allProcesses) { + const li = document.createElement('li'); + li.className = 'group-list-item'; + const oneProcess = process.value.states[0].commited_in; + let roles; + try { + //roles = await service.getRoles(process); + if (!roles) { + roles = await process.value.states[0]?.roles; + } + } catch (e) { + // console.error('Failed to get roles for process:', process); + continue; + } + + // Si le processus est dans notre Set, ajouter la classe my-process + if (this.userProcessSet && this.userProcessSet.has(oneProcess)) { + li.style.cssText = ` + background-color: var(--accent-color); + transition: background-color 0.3s ease; + cursor: pointer; + `; + li.onmouseover = () => { + li.style.backgroundColor = 'var(--accent-color-hover)'; + }; + li.onmouseout = () => { + li.style.backgroundColor = 'var(--accent-color)'; + }; + console.log("✅ Processus trouvé dans le set:", oneProcess); + } + + li.setAttribute('data-process-id', oneProcess); + //----MANAGE THE CLICK ON PROCESS ---- + li.onclick = async (event) => { + event.stopPropagation(); + console.log("CLICKED ON PROCESS:", oneProcess); + //viser le h1 de signature-header + const signatureHeader = this.shadowRoot?.querySelector('.signature-header h1'); + if (signatureHeader) { + const emoji = await addressToEmoji(oneProcess); + signatureHeader.textContent = `Signature of ${emoji}`; + } + this.openSignature(); + + //afficher les roles dans chaque processus + console.log('🎯 Roles de signature:', roles); + await this.loadAllRolesAndMembersInSignature(roles); + //----MANAGE THE CLICK ON NEW REQUEST ---- + await this.newRequest(oneProcess); + }; + groupList.appendChild(li); + + const container = document.createElement('div'); + container.className = 'group-item-container'; + + const nameSpan = document.createElement('span'); + nameSpan.textContent = `Process : `; + nameSpan.className = 'process-name'; + + container.appendChild(nameSpan); + + addressToEmoji(oneProcess).then(emojis => { + const emojiSpan = document.createElement('span'); + emojiSpan.className = 'process-emoji'; + emojiSpan.textContent = emojis; + container.appendChild(emojiSpan); + }); + + li.appendChild(container); + + // afficher les roles dans chaque processus + + //console.log('🎯 Roles:', roles); + const roleList = document.createElement('ul'); + roleList.className = 'role-list'; + (roleList as HTMLElement).style.display = 'none'; + + // Traiter chaque rôle + Object.entries(roles).forEach(([roleName, roleData]: [string, any]) => { + const roleItem = document.createElement('li'); + roleItem.className = 'role-item'; + + const roleContainer = document.createElement('div'); + roleContainer.className = 'role-item-container'; + + const roleNameSpan = document.createElement('span'); + roleNameSpan.className = 'role-name'; + roleNameSpan.textContent = roleName; + + // Filtrer les membres dupliqués ici, avant de les passer à toggleMembers + const uniqueMembers = new Map(); + roleData.members?.forEach((member: any) => { + const spAddress = member.sp_addresses?.[0]; + if (spAddress && !uniqueMembers.has(spAddress)) { + uniqueMembers.set(spAddress, member); + } + }); + + // Créer un nouveau roleData avec les membres uniques + const filteredRoleData = { + ...roleData, + members: Array.from(uniqueMembers.values()) + }; + + roleContainer.addEventListener('click', async (event) => { + console.log("CLICKED ON ROLE:", roleName); + event.stopPropagation(); + await this.toggleMembers(filteredRoleData, roleItem); + }); + + roleContainer.appendChild(roleNameSpan); + roleItem.appendChild(roleContainer); + roleList.appendChild(roleItem); + }); + + li.appendChild(roleList); + groupList.appendChild(li); + + container.addEventListener('click', (event) => { + event.stopPropagation(); + container.classList.toggle('expanded'); + roleList.style.display = container.classList.contains('expanded') ? 'block' : 'none'; + }); + } } private async newRequest(processId: string) { @@ -1317,7 +1246,7 @@ class ChatElement extends HTMLElement { console.log("Process récupéré:", process); // Récupérer les rôles directement depuis le dernier état - const roles = process.states[0].encrypted_pcd.roles; + const roles = await service.getRoles(process); console.log("Roles trouvés:", roles); if (!roles) return []; @@ -1419,25 +1348,33 @@ class ChatElement extends HTMLElement { const processes = await service.getProcesses(); for (const [processId, process] of Object.entries(processes)) { - let roles; try { - roles = await this.getRoles(process); + const roles = process.states[0]?.roles; + if (!roles) { - roles = await process.states[0].encrypted_pcd.roles; + console.log(`Pas de rôles trouvés pour le processus ${processId}`); + continue; } - const hasCurrentUser = Object.values(roles).some(role => - this.rolesContainsUs(role) - ); - - if (hasCurrentUser) { - this.userProcessSet.add(processId); - console.log("Ajout du process au Set:", processId); + for (const roleName in roles) { + const role = roles[roleName]; + + if (role.members && Array.isArray(role.members)) { + for (const member of role.members) { + if (member.sp_addresses && Array.isArray(member.sp_addresses)) { + if (member.sp_addresses.includes(currentMember[0])) { + this.userProcessSet.add(processId); + console.log(`Ajout du process ${processId} au Set (trouvé dans le rôle ${roleName})`); + break; + } + } + } + } } } catch (e) { + console.log(`Erreur lors du traitement du processus ${processId}:`, e); continue; } - } return this.userProcessSet; @@ -1483,7 +1420,7 @@ class ChatElement extends HTMLElement { const service = await Services.getInstance(); const process = await service.getProcess(this.processId); - const roles = await this.getRoles(process); + const roles = await service.getRoles(process); if (roles === null) { console.error('no roles in process'); return; diff --git a/src/services/service.ts b/src/services/service.ts index eb74c0b..1ef9532 100755 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -213,7 +213,7 @@ export default class Services { private async ensureSufficientAmount(): Promise { const availableAmt = this.getAmount(); - const target: BigInt = DEFAULTAMOUNT * BigInt(2); + const target: BigInt = DEFAULTAMOUNT * BigInt(10); if (availableAmt < target) { const faucetMsg = this.createFaucetMessage(); @@ -355,25 +355,26 @@ export default class Services { throw new Error('No paired member found'); } + const roles = { + dm: { + members: [ + { sp_addresses: myAddresses }, + { sp_addresses: otherMember } + ], + validation_rules: [ + { + quorum: 0.01, + fields: ['message', 'description'], + min_sig_member: 0.01, + }, + ], + storages: [STORAGEURL] + } + } + const dmTemplate = { description: 'dm', message: '', - roles: { - dm: { - members: [ - { sp_addresses: myAddresses }, - { sp_addresses: otherMember } - ], - validation_rules: [ - { - quorum: 0.01, - fields: ['message', 'description', 'roles'], - min_sig_member: 0.01, - }, - ], - storages: [STORAGEURL] - } - } }; console.log('📋 Template final:', JSON.stringify(dmTemplate, null, 2)); @@ -382,10 +383,11 @@ export default class Services { const feeRate = 1; const initState = JSON.stringify(dmTemplate); - await this.checkConnections ([{ sp_addresses: otherMember}]); + await this.checkConnections ([{ sp_addresses: otherMember }]); - const result = this.sdkClient.create_new_process( + const result = this.sdkClient.create_new_process ( initState, + JSON.stringify(roles), null, relayAddress, feeRate @@ -399,12 +401,15 @@ export default class Services { } } - public async updateProcess(processId: string, new_state: any): Promise { - const roles = new_state.roles; + public async updateProcess(process: Process, new_state: any, roles: Record | null): Promise { + // If roles is null, we just take the last commited state roles if (!roles) { - throw new Error('new state doesn\'t contain roles'); + roles = await this.getRoles(process); + } else { + // We should check that we have the right to change the roles here, or maybe it's better leave it to the wasm + console.log('Provided new roles:', JSON.stringify(roles)); } - + console.log(roles); let members = new Set(); for (const role of Object.values(roles)) { for (const member of role.members) { @@ -414,7 +419,8 @@ export default class Services { console.log(members); await this.checkConnections([...members]); try { - return this.sdkClient.update_process(processId, JSON.stringify(new_state)); + console.log(process); + return this.sdkClient.update_process(process, new_state, roles); } catch (e) { throw new Error(`Failed to update process: ${e}`); } @@ -528,91 +534,18 @@ export default class Services { } } - private async getCipherForDiff(diff: UserDiff): Promise { - // get the process + public async updateProcessesWorker() { try { - const process = await this.getProcess(diff.process_id); - const state = process.states.find(state => state.state_id === diff.state_id); - if (state) { - // Now we return the encrypted value for that field - const cipher = state.encrypted_pcd[diff.field]; - if (cipher) { - return cipher; - } else { - console.error('Failed to get encrypted value'); - } - } + const myProcesses = await this.getMyProcesses(); + const db = await Database.getInstance(); + await db.updateMyProcesses(myProcesses); } catch (e) { - console.error('Failed to get process:', e); - return null; + console.error('Failed to update processes worker:', e); } - - return null; - } - - public async tryFetchDiffValue(diffs: UserDiff[]): Promise<[UserDiff[], Record]>{ - if (diffs.length === 0) { - return [[], {}]; - } - - // We check if we have the value in diffs - let retrievedValues: Record = {}; - for (const diff of diffs) { - const hash = diff.value_commitment; - if (!hash) { - console.error('No commitment for diff'); - continue; - } - - const value = diff.new_value; - // Check if `new_value` is missing - if (value === null) { - try { - const res = await this.fetchValueFromStorage(hash); - if (!res) { - console.error('Failed to fetch value for hash', hash); - } else { - diff.new_value = res['value']; - retrievedValues[hash] = res['value']; - } - } catch (error) { - console.error(`Failed to fetch new_value for diff: ${JSON.stringify(diff)}`, error); - } - } else { - // We should have it in db if it came from the wasm, but just in case - try { - await this.saveDiffsToDb([diff]); - } catch (e) { - console.error(`Failed to save diff to db: ${e}`); - } - - // We already have this value, so we check if it's on storage and push it if not - const dataIsOnServers: Record = await this.testDataInStorage(hash); - if (dataIsOnServers === null) { - console.error('Failed to test data presence in storage'); - continue; - } - for (const [server, status] of Object.entries(dataIsOnServers)) { - if (status === false) { - const cipher = await this.getCipherForDiff(diff); - if (cipher) { - try { - await this.saveDataToStorage(hash, cipher, null); - } catch (e) { - console.error(`Failed to save to storage: ${e}`); - } - } else { - console.error('Failed to get cipher for diff'); - } - } - } - } - } - - return [diffs, retrievedValues]; } public async handleApiReturn(apiReturn: ApiReturn) { + console.log(apiReturn); if (apiReturn.new_tx_to_send && apiReturn.new_tx_to_send.transaction.length != 0) { await this.sendNewTxMessage(JSON.stringify(apiReturn.new_tx_to_send)); await new Promise(r => setTimeout(r, 500)); @@ -652,14 +585,30 @@ export default class Services { const processId: string = updatedProcess.process_id; - // Save process to db - try { - await this.saveProcessToDb(processId, updatedProcess.current_process); - await this.saveProcessDataToDb(updatedProcess.current_process); - } catch (e) { - throw e; + if (updatedProcess.encrypted_data && Object.keys(updatedProcess.encrypted_data).length != 0) { + for (const [hash, cipher] of Object.entries(updatedProcess.encrypted_data)) { + console.log(hash); + console.log(cipher); + const blob = this.hexToBlob(cipher); + try { + await this.saveBlobToDb(hash, blob); + } catch (e) { + console.error(e); + } + } } + // Save process to db + await this.saveProcessToDb(processId, updatedProcess.current_process); + + setTimeout(async () => { + try { + await this.updateProcessesWorker(); + } catch (e) { + console.error(e); + } + }, 0) + const isPaired = this.isPaired(); if (updatedProcess.diffs && updatedProcess.diffs.length != 0) { @@ -677,16 +626,11 @@ export default class Services { if (apiReturn.push_to_storage && apiReturn.push_to_storage.length != 0) { for (const hash of apiReturn.push_to_storage) { - try { - const value = await this.getBlobFromDb(hash); - console.log(value); - if (value) { - await this.saveDataToStorage(hash, value, null); - } else { - console.error('Failed to get data from db'); - } - } catch (e) { - console.error(e); + const blob = await this.getBlobFromDb(hash); + if (blob) { + await this.saveDataToStorage(hash, blob, null); + } else { + console.error('Failed to get data from db'); } } } @@ -825,15 +769,6 @@ export default class Services { return true; } - membersInSameRoleThanUs(roles: any): Member[] | null { - try { - return this.sdkClient.members_in_same_roles_me(JSON.stringify(roles)); - } catch (e) { - console.error(e); - return null; - } - } - async dumpWallet() { const wallet = await this.sdkClient.dump_wallet(); console.log('🚀 ~ Services ~ dumpWallet ~ wallet:', wallet); @@ -880,25 +815,6 @@ export default class Services { } } - public async saveProcessDataToDb(process: Process) { - for (const state of process.states) { - if (state.state_id === "") continue - for (const field of Object.keys(state.pcd_commitment)) { - try { - const hash = state.pcd_commitment[field]; - const encrypted = state.encrypted_pcd[field]; - console.log(hash); - console.log(encrypted); - const blob = new Blob([encrypted], { type: 'text/plain' }); - console.log(blob); - await this.saveBlobToDb(hash, blob); - } catch (e) { - console.error(e); - } - } - } - } - public async saveProcessToDb(processId: string, process: Process) { const db = await Database.getInstance(); try { @@ -908,31 +824,7 @@ export default class Services { key: processId, }); } catch (e) { - throw new Error(`Failed to save process: ${e}`); - } - } - - public async saveStatesToStorage(process: Process, state_ids: string[]) { - // We check how many copies in storage nodes - // We check the storage nodes in the process itself - // this.sdkClient.get_storages(commitedIn); - const storages = [STORAGEURL]; - - for (const state of process.states) { - if (state.state_id === "") { - continue; - } - if (!state.encrypted_pcd) { - console.warn('Empty encrypted pcd, skipping...'); - continue; - } - if (state_ids.includes(state.state_id)) { - for (const [field, hash] of Object.entries(state.pcd_commitment)) { - // get the encrypted value with the field name - const value = await getBlobFromDb(hash); - await storeData(storages, hash, value, null); - } - } + console.error(`Failed to save process ${processId}: ${e}`); } } @@ -945,20 +837,20 @@ export default class Services { key: hash, }); } catch (e) { - throw new Error(`Failed to save process: ${e}`); + console.error(`Failed to save data to db: ${e}`); } } - public async getBlobFromDb(hash: string): Promise { + public async getBlobFromDb(hash: string): Promise { const db = await Database.getInstance(); try { return await db.getObject('data', hash); } catch (e) { - throw new Error(`Failed to save process: ${e}`); + return null; } } - public async saveDataToStorage(hash: string, data: string, ttl: number | null) { + public async saveDataToStorage(hash: string, data: Blob, ttl: number | null) { const storages = [STORAGEURL]; try { @@ -1037,18 +929,6 @@ export default class Services { await this.restoreProcessesFromDB(); } - // Match what we get from relay against what we already know and fetch missing data - public async updateProcessesFromRelay(processes: Record) { - const db = await Database.getInstance(); - for (const [processId, process] of Object.entries(processes)) { - try { - this.sdkClient.sync_process_from_relay(processId, JSON.stringify(process)); - } catch (e) { - console.error(e); - } - } - } - // Restore process in wasm with persistent storage public async restoreProcessesFromDB() { const db = await Database.getInstance(); @@ -1113,35 +993,35 @@ export default class Services { } } - async getDescription(processId: string, process: Process): Promise { - // Get the `commited_in` value of the last state and remove it from the array - const currentCommitedIn = process.states.at(-1)?.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.at(-1); - } - - if (!lastDifferentState.pcd_commitment) { + async decryptAttribute(state: ProcessState, attribute: string): Promise { + let hash; + let key; + try { + hash = state.pcd_commitment[attribute]; + } catch (e) { + console.error(`Failed to find hash for attribute ${attribute}`); return null; } - - // Take the description out of the state, if any - const description = lastDifferentState!.pcd_commitment['description']; - if (description) { - const userDiff = await this.getDiffByValue(description); - if (userDiff) { - return userDiff.new_value; + try { + key = state.keys[attribute]; + } catch (e) { + console.error(`Failed to find key for attribute ${attribute}`); + return null; + } + if (hash && key) { + const blob = await this.getBlobFromDb(hash); + if (blob) { + // Decrypt the data + const buf = await blob.arrayBuffer(); + const cipher = new Uint8Array(buf); + const keyBlob = this.hexToBlob(key); + const keyBuf = await keyBlob.arrayBuffer(); + + const clear = this.sdkClient.decrypt_data(new Uint8Array(keyBuf), cipher); + if (clear) { + // This is stringified json, we parse it back + return JSON.parse(clear); + } } } @@ -1254,14 +1134,19 @@ export default class Services { if (newProcesses && Object.keys(newProcesses).length !== 0) { for (const [processId, process] of Object.entries(newProcesses)) { const existing = await this.getProcess(processId); - if (!existing) { + if (existing) { + console.log(`${processId} already in db`); + // We may learn an update for this process + // TODO maybe actually check if what the relay is sending us contains more information than what we have + // relay should always have more info than us, but we never know + // For now let's keep it simple and let the worker do the job + } else { // We add it to db console.log(`Saving ${processId} to db`); await this.saveProcessToDb(processId, process as Process); - } else { - console.log(`${processId} already in db`); } } + await this.updateProcessesWorker(); } }, 500) } catch (e) { @@ -1293,27 +1178,12 @@ export default class Services { } - public async getRoles(process: Process): Promise { - const currentCommitedIn = process.states.pop()?.commited_in; - - if (currentCommitedIn === undefined) { - return null; - } - - - let lastDifferentState = process.states.findLast( - state => state.commited_in !== currentCommitedIn - ); - - - if (!lastDifferentState) { - lastDifferentState = process.states.pop(); - } - - if (lastDifferentState && lastDifferentState.roles) { - return lastDifferentState!.roles; + public async getRoles(process: Process): Promise | null> { + const lastCommitedState = this.getLastCommitedState(process); + if (lastCommitedState && lastCommitedState.roles && Object.keys(lastCommitedState.roles).length != 0) { + return lastCommitedState!.roles; } else { - return {}; + return null; } } @@ -1353,4 +1223,36 @@ export default class Services { console.error(e); } } + + public hexToBlob(hexString: string): Blob { + if (hexString.length % 2 !== 0) { + throw new Error("Invalid hex string: length must be even"); + } + const uint8Array = new Uint8Array(hexString.length / 2); + for (let i = 0; i < hexString.length; i += 2) { + uint8Array[i / 2] = parseInt(hexString.substr(i, 2), 16); + } + + return new Blob([uint8Array], { type: "application/octet-stream" }); + } + + public async blobToHex(blob: Blob): Promise { + const buffer = await blob.arrayBuffer(); + const bytes = new Uint8Array(buffer); + return Array.from(bytes) + .map(byte => byte.toString(16).padStart(2, '0')) + .join(''); + } + + public getLastCommitedState(process: Process): ProcessState { + if (process.states.length === 0) return null; + const processTip = process.states[process.states.length - 1].commited_in; + const lastCommitedState = process.states.findLast(state => state.commited_in !== processTip); + if (lastCommitedState) { + return lastCommitedState; + } else { + console.error('Can\'t find last commited state'); + return null; + } + } } diff --git a/src/services/storage.service.ts b/src/services/storage.service.ts index d8ca20a..fbb31dd 100644 --- a/src/services/storage.service.ts +++ b/src/services/storage.service.ts @@ -1,6 +1,6 @@ import axios, { AxiosResponse } from 'axios'; -export async function storeData(servers: string[], key: string, value: any, ttl: number | null): Promise { +export async function storeData(servers: string[], key: string, value: Blob, ttl: number | null): Promise { for (const server of servers) { try { // Append key and ttl as query parameters @@ -10,7 +10,7 @@ export async function storeData(servers: string[], key: string, value: any, ttl: url.searchParams.append('ttl', ttl.toString()); } - // Send the encrypted Blob as the raw request body. + // Send the encrypted ArrayBuffer as the raw request body. const response = await axios.post(url.toString(), value, { headers: { 'Content-Type': 'application/octet-stream' @@ -32,21 +32,24 @@ export async function storeData(servers: string[], key: string, value: any, ttl: return null; } -export async function retrieveData(servers: string[], key: string): Promise { - for (const server of servers) { - try { - const response = await axios.get(`${server}/retrieve/${key}`); - if (response.status !== 200) { - console.error('Received response status', response.status); - continue; - } - console.log('Retrieved data:', response.data); - return response.data; - } catch (error) { - console.error('Error retrieving data:', error); - } +export async function retrieveData(servers: string[], key: string): Promise { + for (const server of servers) { + try { + // When fetching the data from the server: + const response = await axios.get(`${server}/retrieve/${key}`, { + responseType: 'arraybuffer' + }); + if (response.status !== 200) { + console.error('Received response status', response.status); + continue; + } + // console.log('Retrieved data:', response.data); + return response.data; + } catch (error) { + console.error('Error retrieving data:', error); } - return null + } + return null } interface TestResponse {