diff --git a/process.diff b/process.diff new file mode 100644 index 0000000..57bd428 --- /dev/null +++ b/process.diff @@ -0,0 +1,203 @@ +diff --git a/src/pages/process/process.ts b/src/pages/process/process.ts +index 27c1301..a4f8943 100755 +--- a/src/pages/process/process.ts ++++ b/src/pages/process/process.ts +@@ -2,9 +2,15 @@ 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'; ++ ++let myProcesses = new Set(); ++let allProcesses = new Set(); + + // 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 +@@ -43,6 +49,23 @@ export async function init() { + wrapper.appendChild(search_div); + + addPlaceholder(wrapper); ++ ++ await loadAllProcesses(); ++ ++ const database = await Database.getInstance(); ++ ++ try { ++ await database.updateMyProcesses({ myProcessesId: Array.from(myProcesses) }); ++ const updateProcesses = await database.updateMyProcesses({ myProcessesId: Array.from(myProcesses) }); ++ console.log("UPDATE PROCESSES d'INIT: ", updateProcesses); ++ } catch (error) { ++ console.error("Error updating my processes:", error); ++ } ++ ++ ++ ++ ++ + } + + function removePlaceholder(wrapper: HTMLElement) { +@@ -155,62 +178,39 @@ function clearAutocompleteList(select: HTMLSelectElement) { + if (autocomplete_list) autocomplete_list.innerHTML = ''; + } + +-// Populate the autocomplete list following a given query from the user +-function populateAutocompleteList(select: HTMLSelectElement, query: string, dropdown = false) { ++async function populateAutocompleteList(select: HTMLSelectElement, query: string, dropdown = false) { + const { autocomplete_options } = getOptions(select); + +- let options_to_show; ++ let options_to_show = []; + +- if (dropdown) { +- let messagingCounter = 1; +- const messagingOptions = select.querySelectorAll('option[value="messaging"]'); +- +- options_to_show = autocomplete_options.map(option => { +- if (option === 'messaging') { +- // Récupérer l'élément option correspondant au compteur actuel +- const currentOption = messagingOptions[messagingCounter - 1]; +- const processId = currentOption?.getAttribute('data-process-id'); +- console.log(`Mapping messaging ${messagingCounter} with processId:`, processId); +- +- const optionText = `messaging ${messagingCounter}`; +- messagingCounter++; +- +- // Stocker le processId dans un attribut data sur le select +- select.setAttribute(`data-messaging-id-${messagingCounter - 1}`, processId || ''); +- +- return optionText; +- } +- return option; +- }); +- } else { +- options_to_show = autocomplete(query, autocomplete_options); +- } ++ console.log(myProcesses); ++ ++ const mineArray = Array.from(myProcesses); ++ const allArray = Array.from(allProcesses).filter(id => !myProcesses.has(id)); + + 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 result_size = options_to_show.length; + +- if (result_size == 1) { ++ const addProcessToList = (processId:string, isMine: boolean) => { + const li = document.createElement('li'); +- li.innerText = options_to_show[0]; +- li.setAttribute('data-value', options_to_show[0]); ++ 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); +- if (query.length == options_to_show[0].length) { +- const event = new Event('click'); +- li.dispatchEvent(event); +- } +- } else if (result_size > 1) { +- for (let i = 0; i < result_size; i++) { +- const li = document.createElement('li'); +- li.innerText = options_to_show[i]; +- li.setAttribute('data-value', options_to_show[i]); +- if (li) addSubscription(li, 'click', selectOption); +- autocomplete_list?.appendChild(li); +- } +- } else { ++ }; ++ ++ mineArray.forEach(processId => addProcessToList(processId, true)); ++ allArray.forEach(processId => addProcessToList(processId, false)); ++ ++ if (myProcesses.size === 0 && allProcesses.size === 0) { + const li = document.createElement('li'); + li.classList.add('not-cursor'); + li.innerText = 'No options found'; +@@ -392,6 +392,20 @@ addSubscription(document, 'click', () => { + } + }); + ++async function loadAllProcesses() { ++ try { ++ const [allProcessesNew, myProcessesNew] = await Promise.all([ ++ getProcesses(), ++ getMyProcesses() ++ ]); ++ ++ myProcesses = myProcesses.union(myProcessesNew); ++ allProcesses = allProcesses.union(allProcessesNew); ++ } catch (error) { ++ console.error("Error loading processes:", error); ++ } ++} ++ + async function showSelectedProcess(elem: MouseEvent) { + const container = getCorrectDOM('process-list-4nk-component') as HTMLElement; + +@@ -539,43 +553,36 @@ async function getDescription(processId: string, process: Process): Promise { ++async function getProcesses(): Promise> { + const service = await Services.getInstance(); + const processes = await service.getProcesses(); ++ const processIds = new Set(Object.keys(processes)); + +- const res = Object.entries(processes).map(([key, value]) => ({ +- key, +- value, +- })); +- +- return res; ++ return processIds; + } + +-async function getMyProcesses() { ++async function getMyProcesses(): Promise> { + const service = await Services.getInstance(); + try { + const processes = await service.getProcesses(); +- const userProcessSet = new Set(); +- ++ const userProcessSet = new Set(); ++ + for (const [processId, process] of Object.entries(processes)) { + let roles; + try { +- roles = await this.getRoles(process); ++ roles = await service.getRoles(process); + if (!roles) { + roles = await process.states[0].encrypted_pcd.roles; + } ++ console.log("ROLES: ", roles); ++ ++ const hasCurrentUser = service.rolesContainsUs(roles); + +- const hasCurrentUser = Object.values(roles).some(role => +- service.rolesContainsUs(role) +- ); +- + if (hasCurrentUser) { + userProcessSet.add(processId); + } +- + } catch (e) { + continue; +- console.error(`Error processing process ${processId}:`, e); + } + } + diff --git a/src/pages/chat/chat.ts b/src/pages/chat/chat.ts index 373eb10..80493a0 100755 --- a/src/pages/chat/chat.ts +++ b/src/pages/chat/chat.ts @@ -383,7 +383,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') @@ -489,7 +489,7 @@ class ChatElement extends HTMLElement { if (description !== "dm") { continue; } - const roles = await this.getRoles(process); + const roles = await service.getRoles(process); if (!service.rolesContainsMember(roles, recipientAddresses)) { console.error('Member is not part of the process'); continue; @@ -911,44 +911,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 tabs.forEach(tab => { @@ -1046,7 +1008,7 @@ class ChatElement extends HTMLElement { const oneProcess = process.states[0].commited_in; let roles; try { - roles = await this.getRoles(process); + roles = await service.getRoles(process); if (!roles) { roles = await process.states[0].encrypted_pcd.roles; } @@ -1421,7 +1383,7 @@ class ChatElement extends HTMLElement { for (const [processId, process] of Object.entries(processes)) { let roles; try { - roles = await this.getRoles(process); + roles = await service.getRoles(process); if (!roles) { roles = await process.states[0].encrypted_pcd.roles; } @@ -1483,7 +1445,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/pages/process/process.ts b/src/pages/process/process.ts index 27c1301..835a4d3 100755 --- a/src/pages/process/process.ts +++ b/src/pages/process/process.ts @@ -2,9 +2,15 @@ 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'; + +let myProcesses = new Set(); +let allProcesses = new Set(); // 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 @@ -43,6 +49,16 @@ export async function init() { wrapper.appendChild(search_div); addPlaceholder(wrapper); + + await loadAllProcesses(); + + const database = await Database.getInstance(); + + try { + await database.updateMyProcesses({ myProcessesId: Array.from(myProcesses) }); + } catch (error) { + console.error("Error updating my processes:", error); + } } function removePlaceholder(wrapper: HTMLElement) { @@ -155,62 +171,39 @@ function clearAutocompleteList(select: HTMLSelectElement) { if (autocomplete_list) autocomplete_list.innerHTML = ''; } -// Populate the autocomplete list following a given query from the user -function populateAutocompleteList(select: HTMLSelectElement, query: string, dropdown = false) { +async function populateAutocompleteList(select: HTMLSelectElement, query: string, dropdown = false) { const { autocomplete_options } = getOptions(select); - let options_to_show; + let options_to_show = []; - if (dropdown) { - let messagingCounter = 1; - const messagingOptions = select.querySelectorAll('option[value="messaging"]'); - - options_to_show = autocomplete_options.map(option => { - if (option === 'messaging') { - // Récupérer l'élément option correspondant au compteur actuel - const currentOption = messagingOptions[messagingCounter - 1]; - const processId = currentOption?.getAttribute('data-process-id'); - console.log(`Mapping messaging ${messagingCounter} with processId:`, processId); - - const optionText = `messaging ${messagingCounter}`; - messagingCounter++; - - // Stocker le processId dans un attribut data sur le select - select.setAttribute(`data-messaging-id-${messagingCounter - 1}`, processId || ''); - - return optionText; - } - return option; - }); - } else { - options_to_show = autocomplete(query, autocomplete_options); - } + console.log(myProcesses); + + const mineArray = Array.from(myProcesses); + const allArray = Array.from(allProcesses).filter(id => !myProcesses.has(id)); 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 result_size = options_to_show.length; - if (result_size == 1) { + const addProcessToList = (processId:string, isMine: boolean) => { const li = document.createElement('li'); - li.innerText = options_to_show[0]; - li.setAttribute('data-value', options_to_show[0]); + 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); - if (query.length == options_to_show[0].length) { - const event = new Event('click'); - li.dispatchEvent(event); - } - } else if (result_size > 1) { - for (let i = 0; i < result_size; i++) { - const li = document.createElement('li'); - li.innerText = options_to_show[i]; - li.setAttribute('data-value', options_to_show[i]); - if (li) addSubscription(li, 'click', selectOption); - autocomplete_list?.appendChild(li); - } - } else { + }; + + mineArray.forEach(processId => addProcessToList(processId, true)); + allArray.forEach(processId => addProcessToList(processId, false)); + + if (myProcesses.size === 0 && allProcesses.size === 0) { const li = document.createElement('li'); li.classList.add('not-cursor'); li.innerText = 'No options found'; @@ -392,6 +385,20 @@ addSubscription(document, 'click', () => { } }); +async function loadAllProcesses() { + try { + const [allProcessesNew, myProcessesNew] = await Promise.all([ + getProcesses(), + getMyProcesses() + ]); + + myProcesses = myProcesses.union(myProcessesNew); + allProcesses = allProcesses.union(allProcessesNew); + } catch (error) { + console.error("Error loading processes:", error); + } +} + async function showSelectedProcess(elem: MouseEvent) { const container = getCorrectDOM('process-list-4nk-component') as HTMLElement; @@ -539,43 +546,36 @@ async function getDescription(processId: string, process: Process): Promise { +async function getProcesses(): Promise> { const service = await Services.getInstance(); const processes = await service.getProcesses(); + const processIds = new Set(Object.keys(processes)); - const res = Object.entries(processes).map(([key, value]) => ({ - key, - value, - })); - - return res; + return processIds; } -async function getMyProcesses() { +async function getMyProcesses(): Promise> { const service = await Services.getInstance(); try { const processes = await service.getProcesses(); - const userProcessSet = new Set(); - + const userProcessSet = new Set(); + for (const [processId, process] of Object.entries(processes)) { let roles; try { - roles = await this.getRoles(process); + roles = await service.getRoles(process); if (!roles) { roles = await process.states[0].encrypted_pcd.roles; } + console.log("ROLES: ", roles); + + const hasCurrentUser = service.rolesContainsUs(roles); - const hasCurrentUser = Object.values(roles).some(role => - service.rolesContainsUs(role) - ); - if (hasCurrentUser) { userProcessSet.add(processId); } - } catch (e) { continue; - console.error(`Error processing process ${processId}:`, e); } } diff --git a/src/service-workers/database.worker.js b/src/service-workers/database.worker.js index a205b20..5753d1b 100755 --- a/src/service-workers/database.worker.js +++ b/src/service-workers/database.worker.js @@ -1,3 +1,6 @@ +let myProcessesId = new Set(); +let toDownload = []; + self.addEventListener('install', (event) => { event.waitUntil(self.skipWaiting()); // Activate worker immediately }); @@ -23,64 +26,33 @@ self.addEventListener('message', async (event) => { data: itemsWithFlag, }); }; + const scanMissingData = async () => { + const myProcesses = getProcesses(myProcessesId); + + + + event.ports[0].postMessage({ + type: 'TO_DOWNLOAD', + data: toDownload, + }); + } fetchNotifications(); setInterval(fetchNotifications, 2 * 60 * 1000); + scanMissingData(); + setInterval(scanMissingData, 2 * 1000); } - - if (data.type === 'SCAN_PROCESS') { + + if (data.type === 'UPDATE_PROCESSES') { try { - const { myProcessesId } = data.payload; - const db = await openDatabase(); - - // Créer un tableau pour stocker toutes les promesses de processus - const processPromises = myProcessesId.map(async (processId) => { - // Récupérer le processus - const process = await new Promise((resolve, reject) => { - const tx = db.transaction('processes', 'readonly'); - const store = tx.objectStore('processes'); - const request = store.get(processId); - request.onsuccess = () => resolve(request.result); - request.onerror = () => reject(request.error); - }); - - if (!process || !process.states || process.states.length === 0) { - throw new Error(`Process ${processId} not found or invalid`); - } - - // Récupérer les diffs pour chaque état - const diffPromises = process.states.map(async (state) => { - return new Promise((resolve, reject) => { - const tx = db.transaction('diffs', 'readonly'); - const store = tx.objectStore('diffs'); - for (const hash of state.pcd_commitment) { - const request = store.get(hash); - - request.onsuccess = () => resolve(request.result); - request.onerror = () => reject(request.error); - } - }); - }); - - const diffs = await Promise.all(diffPromises); - process.diffs = diffs.filter(diff => diff != null); - - - return process; - }); - - const results = await Promise.all(processPromises); + const { processIds } = data.payload; + for (const processId of processIds) { + myProcessesId.add(processId); + } + console.log(myProcessesId); - event.ports[0].postMessage({ - status: 'success', - message: 'All processes scanned', - data: results - }); - - } catch (error) { event.ports[0].postMessage({ status: 'error', message: error.message }); } - setInterval(fetchNotifications, 2 * 1000); } if (data.type === 'ADD_OBJECT') { @@ -123,31 +95,55 @@ async function openDatabase() { // Function to get all processes because it is asynchronous async function getAllProcesses() { + const db = await openDatabase(); return new Promise((resolve, reject) => { + if (!db) { + reject(new Error('Database is not available')); + return; + } const tx = db.transaction('processes', 'readonly'); const store = tx.objectStore('processes'); - // const request = store.openCursor(); - // const processes = []; + const request = store.getAll(); - request.onsuccess = (event) => { - // const cursor = event.target.result; - // if (cursor) { - // processes.push({ key: cursor.key, ...cursor.value }); - // cursor.continue(); - // } else { - // resolve(processes); - // } - const allProcesses = store.getAll(); - resolve(allProcesses); + request.onsuccess = () => { + resolve(request.result); }; - request.onerror = (event) => { - reject(event.target.error); + request.onerror = () => { + reject(request.error); }; }); }; -async function getAllItemsWithFlag() { +async function getProcesses(processIds) { + if (!processIds || processIds.length === 0) { + return []; + } + + const db = await openDatabase(); + if (!db) { + throw new Error('Database is not available'); + } + + const tx = db.transaction('processes', 'readonly'); + const store = tx.objectStore('processes'); + + const requests = processIds.map((processId) => { + return new Promise((resolve) => { + const request = store.get(processId); + request.onsuccess = () => resolve(request.result); + request.onerror = () => { + console.error(`Error fetching process ${processId}:`, request.error); + resolve(undefined); + }; + }); + }); + + const results = await Promise.all(requests); + return results.filter(result => result !== undefined); +} + +async function getAllDiffsNeedValidation() { const db = await openDatabase(); const allProcesses = await getAllProcesses(); diff --git a/src/services/database.service.ts b/src/services/database.service.ts index c470b90..0544ccd 100755 --- a/src/services/database.service.ts +++ b/src/services/database.service.ts @@ -1,13 +1,11 @@ import Services from './service'; -class Database { +export class Database { private static instance: Database; private db: IDBDatabase | null = null; private dbName: string = '4nk'; private dbVersion: number = 1; private serviceWorkerRegistration: ServiceWorkerRegistration | null = null; - private messageChannel: MessageChannel = new MessageChannel(); - private messageChannelForGet: MessageChannel = new MessageChannel(); private storeDefinitions = { AnkLabels: { name: 'labels', @@ -163,10 +161,29 @@ class Database { } }; + private handleUpdateProcessesResponse = async (event: MessageEvent) => { + const data = event.data; + console.log('Received response from service worker (UPDATE_PROCESSES):', data); + for (const process of data.data) { + + console.log("PROCESS: ", process); + + } + }; + // TODO : get the message from the service worker to the client + // we get an object, then we have to loop to look for the diffs + // for each hash in INDEXEDB --> request for true or false + // if the diffs is found, we have to add the diff to the object + // we have to do that for each object + + + + private handleGetObjectResponse = (event: MessageEvent) => { console.log('Received response from service worker (GET_OBJECT):', event.data); }; + public addObject(payload: { storeName: string; object: any; key: any }): Promise { return new Promise((resolve, reject) => { // Check if the service worker is active @@ -202,6 +219,45 @@ class Database { } }); } + + + public updateMyProcesses(payload: { myProcessesId: string[] }): Promise { + return new Promise((resolve, reject) => { + // Check if the service worker is active + if (!this.serviceWorkerRegistration?.active) { + reject(new Error('Service worker is not active')); + return; + } + + // Create a message channel for communication + const messageChannel = new MessageChannel(); + + // Handle the response from the service worker + messageChannel.port1.onmessage = (event) => { + if (event.data.status === 'success') { + resolve(); + } else { + const error = event.data.message; + reject(new Error(error || 'Unknown error occurred while scanning our processes')); + } + }; + + try { + console.log('Sending UPDATE_PROCESSES msg with payload', payload); + this.serviceWorkerRegistration.active.postMessage( + { + type: 'UPDATE_PROCESSES', + + payload, + }, + [messageChannel.port2], + + ); + } catch (error) { + reject(new Error(`Failed to send message to service worker: ${error}`)); + } + }); + } public async getObject(storeName: string, key: string): Promise { const db = await this.getDb(); diff --git a/src/services/service.ts b/src/services/service.ts index fd5fecc..7a2549d 100755 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -524,20 +524,20 @@ export default class Services { // get the process 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'); + } + } } catch (e) { console.error('Failed to get process:', e); return null; } - 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'); - } - } return null; } @@ -1280,37 +1280,35 @@ export default class Services { } - public async getRoles(process: Process): Promise { + public async getRoles(process: Process): Promise> { const currentCommitedIn = process.states.pop()?.commited_in; if (currentCommitedIn === undefined) { - return null; + return {}; } - let lastDifferentState = process.states.findLast( - state => state.commited_in !== currentCommitedIn + state => state.commited_in !== currentCommitedIn ); - if (!lastDifferentState) { - lastDifferentState = process.states.pop(); + lastDifferentState = process.states.pop(); } if (!lastDifferentState || !lastDifferentState.pcd_commitment) { - return null; + return {}; } const roles = lastDifferentState!.pcd_commitment['roles']; if (roles) { - const userDiff = await this.getDiffByValue(roles); - if (userDiff) { - console.log("Successfully retrieved userDiff:", userDiff); - return userDiff.new_value; - } + const userDiff = await this.getDiffByValue(roles); + if (userDiff) { + console.log("Successfully retrieved userDiff:", userDiff); + return userDiff.new_value; + } } - return null; + return {}; } }