WIP (to be recommited smaller later)
This commit is contained in:
parent
a2a7022c08
commit
7bf0fb2b4f
@ -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<string | null> {
|
||||
// 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<any | 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();
|
||||
}
|
||||
|
||||
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<Element>) {
|
||||
// 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<string>) {
|
||||
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 = `
|
||||
<button class="tab active" data-tab="processes">Process</button>
|
||||
<button class="tab" data-tab="members">Members</button>
|
||||
`;
|
||||
groupList.appendChild(tabContent);
|
||||
const tabContent = document.createElement('div');
|
||||
tabContent.className = 'tabs';
|
||||
tabContent.innerHTML = `
|
||||
<button class="tab active" data-tab="processes">Process</button>
|
||||
<button class="tab" data-tab="members">Members</button>
|
||||
`;
|
||||
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<string, any>();
|
||||
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<string, any>();
|
||||
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;
|
||||
|
@ -213,7 +213,7 @@ export default class Services {
|
||||
|
||||
private async ensureSufficientAmount(): Promise<void> {
|
||||
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<ApiReturn> {
|
||||
const roles = new_state.roles;
|
||||
public async updateProcess(process: Process, new_state: any, roles: Record<string, RoleDefinition> | null): Promise<ApiReturn> {
|
||||
// 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<string | null> {
|
||||
// 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<string, string>]>{
|
||||
if (diffs.length === 0) {
|
||||
return [[], {}];
|
||||
}
|
||||
|
||||
// We check if we have the value in diffs
|
||||
let retrievedValues: Record<string, string> = {};
|
||||
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<string, boolean | null> = 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<Blob> {
|
||||
public async getBlobFromDb(hash: string): Promise<Blob | null> {
|
||||
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<string, Process>) {
|
||||
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<string | null> {
|
||||
// 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<string | null> {
|
||||
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<any | null> {
|
||||
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<record<string, RoleDefinition> | 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<string> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
|
||||
export async function storeData(servers: string[], key: string, value: any, ttl: number | null): Promise<AxiosResponse | null> {
|
||||
export async function storeData(servers: string[], key: string, value: Blob, ttl: number | null): Promise<AxiosResponse | null> {
|
||||
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<any | null> {
|
||||
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<ArrayBuffer | null> {
|
||||
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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user