From 4e2546eccc22e84a93b0cab89ad76aa3652d9180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Iv=C3=A1n=20Vieitez=20Parra?= <3857362+corrideat@users.noreply.github.com> Date: Mon, 18 Dec 2023 22:26:49 +0100 Subject: [PATCH] Move preSendChecks to actions. Fix: group-chat fails --- frontend/controller/actions/chatroom.js | 46 +++++++++- frontend/controller/actions/group.js | 88 ++++++++++++++++--- frontend/controller/actions/identity.js | 48 +++++++++- frontend/model/contracts/chatroom.js | 2 +- frontend/model/contracts/group.js | 68 +++++--------- frontend/model/contracts/identity.js | 9 +- frontend/model/contracts/manifests.json | 6 +- .../views/containers/chatroom/ChatMain.vue | 6 ++ .../group-settings/GroupLeaveModal.vue | 7 +- 9 files changed, 200 insertions(+), 80 deletions(-) diff --git a/frontend/controller/actions/chatroom.js b/frontend/controller/actions/chatroom.js index 753d7f2559..559122ae9d 100644 --- a/frontend/controller/actions/chatroom.js +++ b/frontend/controller/actions/chatroom.js @@ -241,7 +241,10 @@ export default (sbp('sbp/selectors/register', { hooks: { ...params?.hooks, preSendCheck (msg, state) { - if (state?.users?.[params.data.username]) return false + console.error('cJ', params.contractID, params.data.username, state.users?.[params.data.username]) + // Avoid sending a duplicate action if the person is already a + // chatroom member + if (state.users?.[params.data.username]) return false if (params?.hooks?.preSendCheck) { return params?.hooks?.preSendCheck(msg, state) } @@ -252,7 +255,46 @@ export default (sbp('sbp/selectors/register', { }), ...encryptedAction('gi.actions/chatroom/rename', L('Failed to rename chat channel.')), ...encryptedAction('gi.actions/chatroom/changeDescription', L('Failed to change chat channel description.')), - ...encryptedAction('gi.actions/chatroom/leave', L('Failed to leave chat channel.')), + ...encryptedAction('gi.actions/chatroom/leave', L('Failed to leave chat channel.'), async (sendMessage, params, signingKeyId) => { + const hooks = { + ...params?.hooks, + preSendCheck (msg, state) { + console.error('cL', params.contractID, params.data.member, state.users?.[params.data.member]) + // Avoid sending a duplicate action if the person isn't a + // chatroom member + if (!state.users?.[params.data.member]) return false + if (params?.hooks?.preSendCheck) { + return params?.hooks?.preSendCheck(msg, state) + } + return true + } + } + + const rootGetters = sbp('state/vuex/getters') + const userID = rootGetters.ourContactProfiles[params.data.member]?.contractID + + const keyIds = userID && sbp('chelonia/contract/foreignKeysByContractID', params.contractID, userID) + + if (keyIds?.length) { + return await sbp('chelonia/out/atomic', { + ...params, + contractName: 'gi.contracts/chatroom', + data: [ + sendMessage({ ...params, returnInvocation: true }), + // Remove the user's CSK from the contract + [ + 'chelonia/out/keyDel', { + data: keyIds + } + ] + ], + signingKeyId, + hooks + }) + } + + return await sendMessage({ ...params, hooks }) + }), ...encryptedAction('gi.actions/chatroom/delete', L('Failed to delete chat channel.')), ...encryptedAction('gi.actions/chatroom/voteOnPoll', L('Failed to vote on a poll.')), ...encryptedAction('gi.actions/chatroom/changeVoteOnPoll', L('Failed to change vote on a poll.')), diff --git a/frontend/controller/actions/group.js b/frontend/controller/actions/group.js index 056e8efce7..f845de04f5 100644 --- a/frontend/controller/actions/group.js +++ b/frontend/controller/actions/group.js @@ -252,11 +252,6 @@ export default (sbp('sbp/selectors/register', { groupContractID: contractID, inviteSecret: serializeKey(CSK, true), creator: true - }, - hooks: { - preSendCheck: (_, state) => { - return !state.groups?.[contractID] - } } }) @@ -433,10 +428,7 @@ export default (sbp('sbp/selectors/register', { ...omit(params, ['options', 'action', 'hooks', 'encryptionKeyId', 'signingKeyId']), hooks: { prepublish: params.hooks?.prepublish, - postpublish: null, - preSendCheck: (_, state) => { - return state?.profiles?.[username]?.status !== PROFILE_STATUS.ACTIVE - } + postpublish: null } }) @@ -625,7 +617,21 @@ export default (sbp('sbp/selectors/register', { }) } - return await sendMessage(omit(params, ['options', 'action'])) + return await sendMessage({ + ...omit(params, ['options', 'action']), + hooks: { + ...params.hooks, + preSendCheck (msg, state) { + console.error('gJC', params.data.chatRoomID, username, state.chatRooms[params.data.chatRoomID]?.users?.[username]?.status) + // Don't send if the member has already been added + if (state.chatRooms[params.data.chatRoomID]?.users?.[username]?.status === PROFILE_STATUS.ACTIVE) return false + if (params?.hooks?.preSendCheck) { + return params?.hooks?.preSendCheck(msg, state) + } + return true + } + } + }) }), 'gi.actions/group/addAndJoinChatRoom': async function (params: GIActionParams) { const message = await sbp('gi.actions/group/addChatRoom', { @@ -678,14 +684,37 @@ export default (sbp('sbp/selectors/register', { (params, e) => L('Failed to remove {member}: {reportError}', { member: params.data.member, ...LError(e) }), async function (sendMessage, params, signingKeyId) { await sendMessage({ - ...omit(params, ['options', 'action']) + ...omit(params, ['options', 'action']), + hooks: { + ...params.hooks, + preSendCheck (msg, state) { + // Don't send if the member has already been removed + if (state?.profiles?.[params.data.member]?.status !== PROFILE_STATUS.ACTIVE) return false + if (params?.hooks?.preSendCheck) { + return params?.hooks?.preSendCheck(msg, state) + } + return true + } + } }) }), ...encryptedAction('gi.actions/group/removeOurselves', (e) => L('Failed to leave group. {codeError}', { codeError: e.message }), async function (sendMessage, params) { + const rootState = sbp('state/vuex/state') await sendMessage({ - ...omit(params, ['options', 'action']) + ...omit(params, ['options', 'action']), + hooks: { + ...params.hooks, + preSendCheck (msg, state) { + // Don't send if we've already been removed + if (state?.profiles?.[rootState.loggedIn.username]?.status !== PROFILE_STATUS.ACTIVE) return false + if (params?.hooks?.preSendCheck) { + return params?.hooks?.preSendCheck(msg, state) + } + return true + } + } }) }), ...encryptedAction('gi.actions/group/changeChatRoomDescription', @@ -877,10 +906,41 @@ export default (sbp('sbp/selectors/register', { sbp('okTurtles.events/emit', OPEN_MODAL, 'AddMembers') } }, - ...encryptedAction('gi.actions/group/leaveChatRoom', L('Failed to leave chat channel.')), + ...encryptedAction('gi.actions/group/leaveChatRoom', L('Failed to leave chat channel.'), async (sendMessage, params) => { + return await sendMessage({ + ...params, + hooks: { + ...params.hooks, + preSendCheck (msg, state) { + // Don't send if the member isn't an active chatroom member + console.error('gLC', params.data.chatRoomID, params.data.member, state.chatRooms[params.data.chatRoomID]?.users?.[params.data.member]?.status) + if (state.chatRooms[params.data.chatRoomID]?.users?.[params.data.member]?.status !== PROFILE_STATUS.ACTIVE) return false + if (params?.hooks?.preSendCheck) { + return params?.hooks?.preSendCheck(msg, state) + } + return true + } + } + }) + }), ...encryptedAction('gi.actions/group/deleteChatRoom', L('Failed to delete chat channel.')), ...encryptedAction('gi.actions/group/invite', L('Failed to create invite.')), - ...encryptedAction('gi.actions/group/inviteAccept', L('Failed to accept invite.')), + ...encryptedAction('gi.actions/group/inviteAccept', L('Failed to accept invite.'), async function (sendMessage, params) { + const rootState = sbp('state/vuex/state') + await sendMessage({ + ...omit(params, ['options', 'action']), + hooks: { + ...params.hooks, + preSendCheck (msg, state) { + if (state?.profiles?.[rootState.loggedIn.username]?.status === PROFILE_STATUS.ACTIVE) return false + if (params?.hooks?.preSendCheck) { + return params?.hooks?.preSendCheck(msg, state) + } + return true + } + } + }) + }), ...encryptedAction('gi.actions/group/inviteRevoke', L('Failed to revoke invite.'), async function (sendMessage, params, signingKeyId) { await sbp('chelonia/out/keyDel', { contractID: params.contractID, diff --git a/frontend/controller/actions/identity.js b/frontend/controller/actions/identity.js index dd58df2dae..99e17fe38b 100644 --- a/frontend/controller/actions/identity.js +++ b/frontend/controller/actions/identity.js @@ -614,8 +614,50 @@ export default (sbp('sbp/selectors/register', { }) } }), - ...encryptedAction('gi.actions/identity/joinDirectMessage', L('Failed to join a direct message.')), - ...encryptedAction('gi.actions/identity/joinGroup', L('Failed to join a group.')), - ...encryptedAction('gi.actions/identity/leaveGroup', L('Failed to leave a group.')), + ...encryptedAction('gi.actions/identity/joinDirectMessage', L('Failed to join a direct message.'), async (sendMessage, params) => { + return await sendMessage({ + ...params, + hooks: { + ...params.hooks, + preSendCheck (msg, state) { + if (has(state.chatRooms, params.data.contractID)) return false + if (params?.hooks?.preSendCheck) { + return params?.hooks?.preSendCheck(msg, state) + } + return true + } + } + }) + }), + ...encryptedAction('gi.actions/identity/joinGroup', L('Failed to join a group.'), async (sendMessage, params) => { + return await sendMessage({ + ...params, + hooks: { + ...params.hooks, + preSendCheck (msg, state) { + if (has(state.groups, params.data.groupContractID)) return false + if (params?.hooks?.preSendCheck) { + return params?.hooks?.preSendCheck(msg, state) + } + return true + } + } + }) + }), + ...encryptedAction('gi.actions/identity/leaveGroup', L('Failed to leave a group.'), async (sendMessage, params) => { + return await sendMessage({ + ...params, + hooks: { + ...params.hooks, + preSendCheck (msg, state) { + if (!has(state.groups, params.data.groupContractID)) return false + if (params?.hooks?.preSendCheck) { + return params?.hooks?.preSendCheck(msg, state) + } + return true + } + } + }) + }), ...encryptedAction('gi.actions/identity/setDirectMessageVisibility', L('Failed to set direct message visibility.')) }): string[]) diff --git a/frontend/model/contracts/chatroom.js b/frontend/model/contracts/chatroom.js index 7d7036352e..2a3582bfbd 100644 --- a/frontend/model/contracts/chatroom.js +++ b/frontend/model/contracts/chatroom.js @@ -746,7 +746,7 @@ sbp('chelonia/defineContract', { hooks: { preSendCheck: (_, state) => { // Only issue OP_KEY_DEL for non-members - return state && !state?.users?.[member] + return !state.users?.[member] } } }).catch(e => { diff --git a/frontend/model/contracts/group.js b/frontend/model/contracts/group.js index 84a72c0990..171575e800 100644 --- a/frontend/model/contracts/group.js +++ b/frontend/model/contracts/group.js @@ -15,7 +15,7 @@ import { } from './shared/constants.js' import { paymentStatusType, paymentType, PAYMENT_COMPLETED } from './shared/payments/index.js' import { createPaymentInfo, paymentHashesFromPaymentPeriod } from './shared/functions.js' -import { cloneDeep, deepEqualJSONType, has, omit, merge } from './shared/giLodash.js' +import { cloneDeep, deepEqualJSONType, omit, merge } from './shared/giLodash.js' import { addTimeToDate, dateToPeriodStamp, compareISOTimestamps, dateFromPeriodStamp, isPeriodStamp, comparePeriodStamps, dateIsWithinPeriod, DAYS_MILLIS, periodStampsForDate, plusOnePeriodLength } from './shared/time.js' import { unadjustedDistribution, adjustedDistribution } from './shared/distribution/distribution.js' import currencies from './shared/currencies.js' @@ -336,14 +336,7 @@ const leaveChatRoomAction = (state, { chatRoomID, member }, meta, leavingGroup) return sbp('gi.actions/chatroom/leave', { contractID: chatRoomID, data: sendingData, - ...extraParams, - hooks: { - preSendCheck: (_, state) => { - // Avoid sending a duplicate action if the person has already - // left or been removed from the chatroom - return state && !!state.users?.[member] - } - } + ...extraParams }).catch((e) => { if ( leavingGroup && @@ -1113,9 +1106,6 @@ sbp('chelonia/defineContract', { chatRoomID: generalChatRoomId }, hooks: { - preSendCheck: (_, state) => { - return state.chatRooms[generalChatRoomId]?.users?.[loggedIn.username]?.status !== PROFILE_STATUS.ACTIVE - }, onprocessed: () => { sbp('state/vuex/commit', 'setCurrentChatRoomId', { groupId: contractID, @@ -1585,11 +1575,6 @@ sbp('chelonia/defineContract', { contractID: identityContractID, data: { groupContractID: contractID - }, - hooks: { - preSendCheck: (_, state) => { - return state && has(state.groups, contractID) - } } }).catch(e => { console.error(`[gi.contracts/group/_cleanup] ${e.name} thrown by gi.contracts/identity/leaveGroup ${identityContractID} for ${contractID}:`, e) @@ -1772,36 +1757,31 @@ sbp('chelonia/defineContract', { return } - if (username === member) { - await sbp('chelonia/contract/sync', chatRoomId) - } + try { + await sbp('chelonia/contract/sync', chatRoomId, { deferredRemove: true }) - if (!sbp('chelonia/contract/hasKeysToPerformOperation', chatRoomId, 'gi.contracts/chatroom/join')) { - return - } - - // Using the group's CEK allows for everyone to have an overview of the - // membership (which is also part of the group contract). This way, - // non-members can remove members when they leave the group - const encryptionKeyId = sbp('chelonia/contract/currentKeyIdByName', state, 'cek', true) - - await sbp('gi.actions/chatroom/join', { - contractID: chatRoomId, - data: { username: member }, - encryptionKeyId, - hooks: { - preSendCheck: (_, state) => { - // Avoid sending a duplicate action if the person is already a - // chatroom member - return !state?.users?.[member] - } + if (!sbp('chelonia/contract/hasKeysToPerformOperation', chatRoomId, 'gi.contracts/chatroom/join')) { + return } - }).catch(e => { - console.error(`Unable to join ${member} to chatroom ${chatRoomId} for group ${contractID}`, e) - }) - if (username === member) { - sbp('state/vuex/commit', 'setCurrentChatRoomId', { groupId: contractID, chatRoomId }) + // Using the group's CEK allows for everyone to have an overview of the + // membership (which is also part of the group contract). This way, + // non-members can remove members when they leave the group + const encryptionKeyId = sbp('chelonia/contract/currentKeyIdByName', state, 'cek', true) + + await sbp('gi.actions/chatroom/join', { + contractID: chatRoomId, + data: { username: member }, + encryptionKeyId + }).catch(e => { + console.error(`Unable to join ${member} to chatroom ${chatRoomId} for group ${contractID}`, e) + }) + + if (username === member) { + sbp('state/vuex/commit', 'setCurrentChatRoomId', { groupId: contractID, chatRoomId }) + } + } finally { + await sbp('chelonia/contract/remove', chatRoomId, { removeIfPending: true }) } }, // eslint-disable-next-line require-await diff --git a/frontend/model/contracts/identity.js b/frontend/model/contracts/identity.js index 3d9bf730f9..b9f811dba4 100644 --- a/frontend/model/contracts/identity.js +++ b/frontend/model/contracts/identity.js @@ -12,7 +12,7 @@ import { noUppercase } from './shared/validators.js' -import { IDENTITY_USERNAME_MAX_CHARS, PROFILE_STATUS } from './shared/constants.js' +import { IDENTITY_USERNAME_MAX_CHARS } from './shared/constants.js' sbp('chelonia/defineContract', { name: 'gi.contracts/identity', @@ -230,12 +230,7 @@ sbp('chelonia/defineContract', { if (has(rootState.contracts, groupContractID)) { await sbp('gi.actions/group/removeOurselves', { contractID: groupContractID, - data: {}, - hooks: { - preSendCheck: (_, state) => { - return state?.profiles?.[rootState.loggedIn.username]?.status === PROFILE_STATUS.ACTIVE - } - } + data: {} }) } diff --git a/frontend/model/contracts/manifests.json b/frontend/model/contracts/manifests.json index a8deb5c966..4d7e44aa29 100644 --- a/frontend/model/contracts/manifests.json +++ b/frontend/model/contracts/manifests.json @@ -1,7 +1,7 @@ { "manifests": { - "gi.contracts/chatroom": "21XWnNGPkCzrD1Az5vCnZSA8Hmms2B5dTt4gay8JF6cX73XV9E", - "gi.contracts/group": "21XWnNXvgCtnnkEgSnf2WAop9NpjpAR1AYJtxaid5JL5iptsaE", - "gi.contracts/identity": "21XWnNPXURFavPWwF5yKXUiFfKakgrAPZ2GadGEBSERgZMq9aN" + "gi.contracts/chatroom": "21XWnNHH2e67b1HMpFupf1EmSWH6ATaEN8yDL73d7nHKyduJru", + "gi.contracts/group": "21XWnNU6B4j7vVX2pYDtDmCTb6U7S3C9q4ijfZkberWfn7dMcT", + "gi.contracts/identity": "21XWnNK49ogfNcAQfanvYPyEbGHRyekriVhAegxAvnWgZyLL8b" } } diff --git a/frontend/views/containers/chatroom/ChatMain.vue b/frontend/views/containers/chatroom/ChatMain.vue index bc9df210d6..9d48390922 100644 --- a/frontend/views/containers/chatroom/ChatMain.vue +++ b/frontend/views/containers/chatroom/ChatMain.vue @@ -12,6 +12,10 @@ emoticons + template(v-for='x in latestEvents') + div + pre(style='overflow-wrap: anywhere; white-space: pre-wrap; font-size: 0.8em') {{ JSON.stringify({...GIMessage.deserializeHEAD(x).head, version: void 0, manifest: void 0 }) }} + .c-body .c-body-conversation( ref='conversation' @@ -162,6 +166,8 @@ export default ({ }, data () { return { + GIMessage, + JSON, config: { isPhone: null }, diff --git a/frontend/views/containers/group-settings/GroupLeaveModal.vue b/frontend/views/containers/group-settings/GroupLeaveModal.vue index 9b391bfdfb..1784b83aee 100644 --- a/frontend/views/containers/group-settings/GroupLeaveModal.vue +++ b/frontend/views/containers/group-settings/GroupLeaveModal.vue @@ -110,12 +110,7 @@ export default ({ const groupContractID = this.currentGroupId await sbp('gi.actions/identity/leaveGroup', { contractID: this.ourIdentityContractId, - data: { groupContractID }, - hooks: { - preSendCheck: (_, state) => { - return !!state.groups?.[this.currentGroupId] - } - } + data: { groupContractID } }) } catch (e) { console.error('GroupLeaveModal submit() error:', e)