This commit is contained in:
NicolasCantu 2025-01-30 10:56:11 +01:00
parent f857422920
commit 157a24accf
3 changed files with 217 additions and 50 deletions

View File

@ -335,10 +335,12 @@ class ChatElement extends HTMLElement {
newState.message = message;
console.log(`Creating state ${newState}`);
// Now we create a new state for the dm process
const apiReturn = service.updateProcess(this.processId, newState);
const apiReturn = await service.updateProcess(this.processId, newState);
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}`);
await service.handleApiReturn(apiReturn);
const createPrdReturn = service.createPrdUpdate(this.processId, newStateId);
@ -347,6 +349,8 @@ class ChatElement extends HTMLElement {
// Now we validate the new state
const approveChangeReturn = service.approveChange(this.processId, newStateId);
await service.handleApiReturn(approveChangeReturn);
await this.loadMemberChat(this.selectedMember);
} catch (error) {
console.error('❌ Error in sendMessage:', error);
}
@ -482,7 +486,6 @@ class ChatElement extends HTMLElement {
const recipientAddresses = memberAddresses.sp_addresses;
for (const [processId, process] of Object.entries(processes)) {
const description = await service.getDescription(processId, process);
// console.log('Process description:', description);
if (description !== "dm") {
continue;
}
@ -555,10 +558,10 @@ class ChatElement extends HTMLElement {
return;
}
// while (dmProcessId === null) {
// dmProcessId = await this.lookForDmProcess();
// await new Promise(r => setTimeout(r, 1000));
// }
while (dmProcessId === null) {
dmProcessId = await this.lookForDmProcess();
await new Promise(r => setTimeout(r, 1000));
}
} else {
console.log('Found DM process', dmProcessId);
this.processId = dmProcessId;
@ -614,10 +617,9 @@ class ChatElement extends HTMLElement {
const messageContent = document.createElement('div');
messageContent.className = 'message user';
// console.log("SENDER: ", message.metadata.sender);
const pairingProcess = await this.getMyProcessId();
const senderEmoji = await addressToEmoji(pairingProcess);
if (message.type === 'file') {
let fileContent = '';
if (message.content.type.startsWith('image/')) {
@ -934,8 +936,6 @@ class ChatElement extends HTMLElement {
return null;
}
console.log('lastDifferentState (roles):', lastDifferentState);
// Take the roles out of the state
const roles = lastDifferentState!.pcd_commitment['roles'];
if (roles) {
@ -943,8 +943,6 @@ class ChatElement extends HTMLElement {
if (userDiff) {
console.log("Successfully retrieved userDiff:", userDiff);
return userDiff.new_value;
} else {
console.log("Failed to retrieve a non-null userDiff.");
}
}
@ -1428,8 +1426,7 @@ class ChatElement extends HTMLElement {
}
const hasCurrentUser = Object.values(roles).some(role =>
(role as { members: { sp_addresses: string[] }[] }).members
.some(member => member.sp_addresses.includes(currentMember[0]))
this.rolesContainsUs(role)
);
if (hasCurrentUser) {

View File

@ -394,7 +394,20 @@ export default class Services {
}
}
public updateProcess(processId: string, new_state: any): ApiReturn {
public async updateProcess(processId: string, new_state: any): Promise<ApiReturn> {
const roles = new_state.roles;
if (!roles) {
throw new Error('new state doesn\'t contain roles');
}
let members = new Set();
for (const role of Object.values(roles)) {
for (const member of role.members) {
members.add(member)
}
}
console.log(members);
await this.checkConnections([...members]);
try {
return this.sdkClient.update_process(processId, JSON.stringify(new_state));
} catch (e) {
@ -510,6 +523,28 @@ export default class Services {
}
}
private async getCipherForDiff(diff: UserDiff): Promise<string | null> {
// get the process
try {
const process = await this.getProcess(diff.process_id);
} 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;
}
public async tryFetchDiffValue(diffs: UserDiff[]): Promise<[UserDiff[], Record<string, string>]>{
if (diffs.length === 0) {
return [[], {}];
@ -518,12 +553,15 @@ export default class Services {
// 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 (diff.new_value === null) {
const hash = diff.value_commitment;
if (!hash) {
console.error('No commitment for diff');
}
if (value === null) {
try {
const res = await this.fetchValueFromStorage(hash);
if (!res) {
@ -535,6 +573,36 @@ export default class Services {
} 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 onStorage = this.testDataInStorage(hash);
if (onStorage === null) {
console.error('Failed to test data presence in storage');
continue;
}
if (!onStorage) {
// We push the encrypted data on storage with default ttl
// We need to take the encrypted data from the state
const cipher = await getCipherForDiff(diff);
if (cipher) {
try {
await this.saveDataToStorage(hash, cipher, null);
} catch (e) {
console.error(`Failed to save to storage: ${e}`);
}
}
} else {
// We could pump the ttl here
// for now, do nothing
continue;
}
}
}
@ -580,19 +648,19 @@ export default class Services {
if (apiReturn.updated_process) {
const updatedProcess = apiReturn.updated_process;
const processId: string = updatedProcess.commitment_tx;
const processId: string = updatedProcess.process_id;
// Save process to storage
// Save process to db
try {
await this.saveProcess(processId, updatedProcess.current_process);
await this.saveProcessToDb(processId, updatedProcess.current_process);
} catch (e) {
throw e;
}
const isPaired = this.isPaired();
if (updatedProcess.new_diffs.length != 0) {
const [updatedDiffs, retrievedValues] = await this.tryFetchDiffValue(updatedProcess.new_diffs);
if (updatedProcess.diffs && updatedProcess.diffs.length != 0) {
const [updatedDiffs, retrievedValues] = await this.tryFetchDiffValue(updatedProcess.diffs);
if (Object.entries(retrievedValues).length != 0) {
const stateId = updatedDiffs[0].state_id;
const processId = updatedDiffs[0].process_id;
@ -602,7 +670,7 @@ export default class Services {
await this.handleApiReturn(apiReturn);
} else {
try {
await this.saveDiffs(updatedDiffs);
await this.saveDiffsToDb(updatedDiffs);
} catch (e) {
throw e;
}
@ -759,6 +827,15 @@ 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);
@ -794,18 +871,31 @@ export default class Services {
}
}
public async saveProcess(commitedIn: string, process: Process) {
private async removeProcess(processId: string): Promise<void> {
const db = await Database.getInstance();
const storeName = 'processes';
try {
await db.deleteObject(storeName, processId);
} catch (e) {
console.error(e);
}
}
public async saveProcessToDb(processId: string, process: Process) {
const db = await Database.getInstance();
try {
await db.addObject({
storeName: 'processes',
object: process,
key: commitedIn,
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);
@ -819,21 +909,39 @@ export default class Services {
console.warn('Empty encrypted pcd, skipping...');
continue;
}
for (const [field, hash] of Object.entries(state.pcd_commitment)) {
// get the encrypted value with the field name
const value = state.encrypted_pcd[field];
await storeData(storages, hash, value, null);
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 = state.encrypted_pcd[field];
await storeData(storages, hash, value, null);
}
}
}
}
public async saveDataToStorage(hash: string, data: string, ttl: number | null) {
const storages = [storageUrl];
try {
await storeData(storages, hash, value, ttl);
} catch (e) {
console.error(`Failed to store data with hash ${hash}: ${e}`);
}
}
public async fetchValueFromStorage(hash: string): Promise<any | null> {
const storages = [storageUrl];
return await retrieveData(storages, hash);
}
public async saveDiffs(diffs: UserDiff[]) {
public async testDataInStorage(hash: string): Promise<boolean | null> {
const storages = [storageUrl];
return await testData(storages, hash);
}
public async saveDiffsToDb(diffs: UserDiff[]) {
const db = await Database.getInstance();
try {
for (const diff of diffs) {
@ -890,20 +998,14 @@ 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)) {
const processFromDb = await this.getProcess(processId);
if (!processFromDb) {
console.log(`Unknown process ${processId}, adding to db`);
await db.addObject({ storeName: 'processes', object: process, key: processId});
} else {
const missingStates = process.states.length - processFromDb.states.length;
if (missingStates < 0) {
// We are missing one or more states for a known process
// TODO send a request to process managers to get the missing states
console.log(`Missing ${missingStates} for process ${processId}`)
}
try {
this.sdkClient.sync_process_from_relay(processId, JSON.stringify(process));
} catch (e) {
console.error(e);
}
}
}
@ -976,7 +1078,6 @@ export default class Services {
const service = await Services.getInstance();
// Get the `commited_in` value of the last state and remove it from the array
const currentCommitedIn = process.states.at(-1)?.commited_in;
console.log('Current CommitedIn:' currentCommitedIn)
if (currentCommitedIn === undefined) {
return null; // No states available
@ -1002,10 +1103,7 @@ export default class Services {
if (description) {
const userDiff = await service.getDiffByValue(description);
if (userDiff) {
console.log("Successfully retrieved userDiff:", userDiff);
return userDiff.new_value;
} else {
console.log("Failed to retrieve a non-null userDiff.");
}
}
@ -1097,6 +1195,7 @@ export default class Services {
}
public async handleHandshakeMsg(url: string, parsedMsg: any) {
const us = this
try {
const handshakeMsg: HandshakeMessage = JSON.parse(parsedMsg);
this.updateRelay(url, handshakeMsg.sp_address);
@ -1104,6 +1203,8 @@ export default class Services {
if (this.membersList && Object.keys(this.membersList).length === 0) {
this.membersList = handshakeMsg.peers_list;
} else {
// console.log('Received members:');
// console.log(handshakeMsg.peers_list);
for (const [processId, member] of Object.entries(handshakeMsg.peers_list)) {
this.membersList[processId] = member;
}
@ -1112,6 +1213,43 @@ export default class Services {
setTimeout(async () => {
const newProcesses = handshakeMsg.processes_list;
if (newProcesses && Object.keys(newProcesses).length !== 0) {
for (const [processId, process] of Object.entries(newProcesses)) {
// We check if we're part of the process
if (process.states.length < 2) continue;
let stateIds = [];
let managers = new Set();
for (const state of process.states) {
if (state.encrypted_pcd === null) continue;
const roles = state.encrypted_pcd['roles'];
if (!roles) {
console.error('Can\'t find roles');
continue;
}
if (this.rolesContainsUs(roles)) {
// We add this state to the list to request
stateIds.push(state.state_id);
} else {
continue;
}
// For now we just add everyone that is in the same role than us
// const sendTo = this.membersInSameRoleThanUs(roles);
for (const [_, role] of Object.entries(roles)) {
if (!role.members.includes(us)) continue;
for (const member of role.members) {
if (member !== us) {
managers.push(member);
}
}
}
}
try {
this.sdkClient.request_data(processId, stateIds, managers);
} catch (e) {
console.error(e);
}
}
await this.updateProcessesFromRelay(newProcesses);
}
}, 500)

View File

@ -4,14 +4,16 @@ export async function storeData(servers: string[], key: string, value: any, ttl:
for (const server of servers) {
try {
const response = await axios.post(`${server}/store`, { key, value, ttl });
console.log('Data stored successfully:', response.data);
console.log('Data stored successfully:', key);
if (response.status !== 200) {
console.error('Received response status', response.status);
continue;
}
console.log('Stored data:', response.data);
return response;
} catch (error) {
if (error?.response?.status === 409) {
return null;
}
console.error('Error storing data:', error);
}
}
@ -34,3 +36,33 @@ export async function retrieveData(servers: string[], key: string): Promise<any
}
return null
}
interface TestResponse {
key: string;
value: boolean;
}
export async function testData(servers: string[], key: string): Promise<boolean | null> {
for (const server of servers) {
try {
const response = await axios.get(`${server}/test/${key}`);
if (response.status !== 200) {
console.error('Test response status', response.status);
continue;
}
const data: TestResponse = response.data;
if (data.value === true) {
return true;
} else {
// Keep looking
continue;
}
} catch (error) {
console.error('Error retrieving data:', error);
}
}
return null;
}