diff --git a/src/services/service.ts b/src/services/service.ts index e287a98..1375729 100755 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -208,12 +208,48 @@ export default class Services { } } - public async checkConnections(members: Member[]): Promise { + // If we're updating a process, we must call that after update especially if roles are part of it + // We will take the roles from the last state, wheter it's commited or not + public async checkConnections(process: Process): Promise { + if (process.states.length < 2) { + throw new Error('Process doesn\'t have any state yet'); + } + let roles = process.states[process.states.length - 2].roles; + if (!roles) { + throw new Error('No roles found'); + } else { + console.log('roles found', roles); + } + let members: Set = new Set(); + for (const role of Object.values(roles!)) { + for (const member of role.members) { + // Check if we know the member that matches this id + const memberAddresses = this.getAddressesForMemberId(member); + if (memberAddresses && memberAddresses.length != 0) { + members.add({ sp_addresses: memberAddresses }); + } + } + } + + if (members.size === 0) { + // This must be a pairing process + // Check if we have a pairedAddresses in the public data + const publicData = process.states[0]?.public_data; + if (!publicData || !publicData['pairedAddresses']) { + throw new Error('Not a pairing process'); + } + const decodedAddresses = this.decodeValue(publicData['pairedAddresses']); + if (decodedAddresses.length === 0) { + throw new Error('Not a pairing process'); + } + members.add({ sp_addresses: decodedAddresses }); + } + // Ensure the amount is available before proceeding await this.getTokensFromFaucet(); - let unconnectedAddresses: Set = new Set(); + let unconnectedAddresses = new Set(); const myAddress = this.getDeviceAddress(); - for (const member of members) { + for (const member of Array.from(members)) { const sp_addresses = member.sp_addresses; if (!sp_addresses || sp_addresses.length === 0) continue; for (const address of sp_addresses) { @@ -226,7 +262,7 @@ export default class Services { } } if (unconnectedAddresses && unconnectedAddresses.size != 0) { - const apiResult = await this.connectAddresses([...unconnectedAddresses]); + const apiResult = await this.connectAddresses(Array.from(unconnectedAddresses)); await this.handleApiReturn(apiResult); } } @@ -374,19 +410,6 @@ export default class Services { console.log('encoded data:', encodedPrivateData); console.log('encoded data:', encodedPublicData); - let members: Set = new Set(); - for (const role of Object.values(roles!)) { - for (const member of role.members) { - // Check if we know the member that matches this id - const memberAddresses = this.getAddressesForMemberId(member); - if (memberAddresses && memberAddresses.length != 0) { - members.add({ sp_addresses: memberAddresses }); - } - } - } - console.log('members:', members); - await this.checkConnections([...members]); - const result = this.sdkClient.create_new_process ( encodedPrivateData, roles, @@ -396,7 +419,12 @@ export default class Services { this.getAllMembers() ); - return(result); + if (result.updated_process) { + await this.checkConnections(result.updated_process); + return(result); + } else { + throw new Error('Empty updated_process in createProcessReturn'); + } } public async updateProcess(process: Process, privateData: Record, publicData: Record, roles: Record | null): Promise { @@ -407,26 +435,6 @@ export default class Services { // 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)); } - let members: Set = new Set(); - for (const role of Object.values(roles!)) { - for (const member of role.members) { - members.add(member) - } - } - if (members.size === 0) { - // This must be a pairing process - // Check if we have a pairedAddresses in the public data - const publicData = this.getPublicData(process); - if (!publicData || !publicData['pairedAddresses']) { - throw new Error('Not a pairing process'); - } - const decodedAddresses = this.decodeValue(publicData['pairedAddresses']); - if (decodedAddresses.length === 0) { - throw new Error('Not a pairing process'); - } - members.add({ sp_addresses: decodedAddresses }); - } - await this.checkConnections([...members]); const privateSplitData = this.splitData(privateData); const publicSplitData = this.splitData(publicData); const encodedPrivateData = { @@ -438,7 +446,13 @@ export default class Services { ...this.sdkClient.encode_binary(publicSplitData.binaryData) }; try { - return this.sdkClient.update_process(process, encodedPrivateData, roles, encodedPublicData, this.getAllMembers()); + const result = this.sdkClient.update_process(process, encodedPrivateData, roles, encodedPublicData, this.getAllMembers()); + if (result.updated_process) { + await this.checkConnections(result.updated_process); + return(result); + } else { + throw new Error('Empty updated_process in updateProcessReturn'); + } } catch (e) { throw new Error(`Failed to update process: ${e}`); } @@ -450,7 +464,13 @@ export default class Services { throw new Error('Unknown process'); } try { - return this.sdkClient.create_update_message(process, stateId, this.getAllMembers()); + const result = this.sdkClient.create_update_message(process, stateId, this.getAllMembers()); + if (result.updated_process) { + await this.checkConnections(result.updated_process); + return(result); + } else { + throw new Error('Empty updated_process in createPrdUpdateReturn'); + } } catch (e) { throw new Error(`Failed to create prd update: ${e}`); } @@ -474,7 +494,13 @@ export default class Services { throw new Error('Failed to get process from db'); } try { - return this.sdkClient.validate_state(process, stateId, this.getAllMembers()); + const result = this.sdkClient.validate_state(process, stateId, this.getAllMembers()); + if (result.updated_process) { + await this.checkConnections(result.updated_process); + return(result); + } else { + throw new Error('Empty updated_process in approveChangeReturn'); + } } catch (e) { throw new Error(`Failed to create prd response: ${e}`); } @@ -1215,6 +1241,7 @@ export default class Services { if (!hasAccess) return null; + await this.checkConnections((await this.getProcess(processId))!); // We should have the key, so we're going to ask other members for it await this.requestDataFromPeers(processId, [state.state_id], [state.roles]); @@ -1378,8 +1405,8 @@ export default class Services { const existing = await this.getProcess(processId); if (existing) { // Look for state id we don't know yet - let new_states = []; - let roles = []; + let newStates: string[] = []; + let newRoles: Record[] = []; for (const state of process.states) { if (!state || !state.state_id) { continue; } // shouldn't happen if (state.state_id === EMPTY32BYTES) { @@ -1387,62 +1414,65 @@ export default class Services { const existingTip = existing.states[existing.states.length - 1].commited_in; if (existingTip !== state.commited_in) { console.log('Found new tip for process', processId); - // We update the process - new_states.push(state.state_id); - roles.push(state.roles); + existing.states.pop(); // We discard the last state + existing.states.push(state); + // We know that's the last state, so we just trigger the update + toSave[processId] = existing; } } else if (!this.lookForStateId(existing, state.state_id)) { + // We don't want to overwrite what we already have for existing processes + // We may end up overwriting the keys for example + // So the process we're going to save needs to merge new states with what we already have + const existingLastState = existing.states.pop(); + if (!existingLastState) { + // This should never happen + console.error('Failed to get last state for process', processId); + break; + } + existing.states.push(state); + existing.states.push(existingLastState); + toSave[processId] = existing; // We mark it for update if (this.rolesContainsUs(state.roles)) { - new_states.push(state.state_id); - roles.push(state.roles); + newStates.push(state.state_id); + newRoles.push(state.roles); + } + } else { + // We already have the state, but we check if we have the keys + const existingState = this.getStateFromId(existing, state.state_id); + if (existingState!.keys && Object.keys(existingState!.keys).length != 0) { + // We have some keys, so we just assume everything ok and move on for now + continue; + } else { + // We verify we are part of the roles + const roles = state.roles; + if (this.rolesContainsUs(roles)) { + // We don't have keys, but we are part of the roles, so we need to request the keys + // that may also be because we are part of a role that don't have any fields + // It's possible but let's request for nothing anyway + newStates.push(state.state_id); + newRoles.push(roles); + } else { + // We are simply not involved, move on + continue; + } } } } - if (new_states.length != 0) { - // We request the new states - // filter out the empty state, if any - // empty state will always be last, so that's easy - if (new_states.findLast(state => state === EMPTY32BYTES)) { - new_states.pop(); - roles.pop(); - } - await this.requestDataFromPeers(processId, new_states, roles); - toSave[processId] = process; + if (newStates.length != 0) { + await this.requestDataFromPeers(processId, newStates, newRoles); } - - // Just to be sure check if that's a pairing process - const lastCommitedState = this.getLastCommitedState(process); - if (lastCommitedState && lastCommitedState.public_data && lastCommitedState.public_data['pairedAddresses']) { - // This is a pairing process - try { - const pairedAddresses = this.decodeValue(lastCommitedState.public_data['pairedAddresses']); - // Are we part of it? - if (pairedAddresses && pairedAddresses.length > 0 && pairedAddresses.includes(this.getDeviceAddress())) { - // We save the process to db - await this.saveProcessToDb(processId, process as Process); - // We update the device - await this.updateDevice(); - } - } catch (e) { - console.error('Failed to check for pairing process:', e); - } - } - // Otherwise we're probably just in the initial loading at page initialization - - // 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`); toSave[processId] = process; } } - await this.batchSaveProcessesToDb(toSave); + if (toSave && Object.keys(toSave).length > 0) { + console.log('batch saving processes to db', toSave); + await this.batchSaveProcessesToDb(toSave); + } } }, 500) } catch (e) {