From 9d1ea65dcd9fd6b22f7d0c2067f32dffa174804f 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, 17 Jun 2024 18:32:32 +0000 Subject: [PATCH] Various bugfixes, related to #2064 and #2066 * eventsAfter: Reject early if the contractID is not set * Fix instances causing such unset contractIDs * Fix group members list to include members with only a contractID * Don't set an empty contract state when receiving OP_KEY_SHARE * Files: laxer validation when removing to process individual files separately --- frontend/controller/actions/identity.js | 13 +++++++++++-- frontend/model/contracts/chatroom.js | 2 +- frontend/model/notifications/templates.js | 4 ++-- frontend/model/state.js | 2 +- frontend/views/components/AvatarUser.vue | 4 +++- shared/domains/chelonia/files.js | 19 +++++++++---------- shared/domains/chelonia/internals.js | 2 +- shared/domains/chelonia/utils.js | 5 +++++ 8 files changed, 33 insertions(+), 18 deletions(-) diff --git a/frontend/controller/actions/identity.js b/frontend/controller/actions/identity.js index 5d46299384..4ef4f7adb8 100644 --- a/frontend/controller/actions/identity.js +++ b/frontend/controller/actions/identity.js @@ -762,6 +762,7 @@ export default (sbp('sbp/selectors/register', { const rootGetters = sbp('state/vuex/getters') const { identityContractID } = sbp('state/vuex/state').loggedIn const { shouldDeleteFile, shouldDeleteToken } = option + let deleteResult if (shouldDeleteFile) { const credentials = Object.fromEntries(manifestCids.map(cid => { @@ -770,15 +771,23 @@ export default (sbp('sbp/selectors/register', { : { billableContractID: identityContractID } return [cid, credential] })) - await sbp('chelonia/fileDelete', manifestCids, credentials) + deleteResult = await sbp('chelonia/fileDelete', manifestCids, credentials) } if (shouldDeleteToken) { await sbp('gi.actions/identity/removeFileDeleteToken', { contractID: identityContractID, - data: { manifestCids } + data: { + manifestCids: deleteResult ? manifestCids.filter((_, i) => { + return deleteResult[i].status === 'fulfilled' + }) : manifestCids + } }) } + + if (deleteResult?.some(r => r.status === 'rejected')) { + throw new Error('[gi.actions/identity/removeFiles] Some CIDs could not be deleted') + } }, 'gi.actions/identity/fetchChatRoomUnreadMessages': async () => { const { ourIdentityContractId } = sbp('state/vuex/getters') diff --git a/frontend/model/contracts/chatroom.js b/frontend/model/contracts/chatroom.js index ed07f67cf0..ae24caf642 100644 --- a/frontend/model/contracts/chatroom.js +++ b/frontend/model/contracts/chatroom.js @@ -511,7 +511,7 @@ sbp('chelonia/defineContract', { shouldDeleteToken: me === data.messageSender } deleteEncryptedFiles(data.manifestCids, option).catch(e => { - console.error(`[gi.contracts/chatroom/deleteMessage/sideEffect] (${contractID}):`, e) + console.warn(`[gi.contracts/chatroom/deleteMessage/sideEffect] (${contractID}):`, e) }) } diff --git a/frontend/model/notifications/templates.js b/frontend/model/notifications/templates.js index 460ad66812..6562241f0e 100644 --- a/frontend/model/notifications/templates.js +++ b/frontend/model/notifications/templates.js @@ -251,12 +251,12 @@ export default ({ }, PAYMENT_THANKYOU_SENT (data: { creatorID: string, fromMemberID: string, toMemberID: string }) { return { - avatarUserID: data.creatorID, + avatarUserID: data.fromMemberID, body: L('{name} sent you a {strong_}thank you note{_strong} for your contribution.', { name: strong(userDisplayNameFromID(data.fromMemberID)), ...LTags('strong') }), - creatorID: data.creatorID, + creatorID: data.fromMemberID, icon: '', level: 'info', linkTo: `/payments?modal=ThankYouNoteModal&from=${data.fromMemberID}&to=${data.toMemberID}`, diff --git a/frontend/model/state.js b/frontend/model/state.js index c0f07f6a76..6f4f813284 100644 --- a/frontend/model/state.js +++ b/frontend/model/state.js @@ -439,7 +439,7 @@ const getters = { .filter(memberID => getters.groupProfiles[memberID] || getters.groupMembersPending[memberID].expires >= Date.now()) .map(memberID => { - const { contractID, displayName, username } = getters.globalProfile(memberID) || groupMembersPending[memberID] || {} + const { contractID, displayName, username } = getters.globalProfile(memberID) || groupMembersPending[memberID] || (getters.groupProfiles[memberID] ? { contractID: memberID } : {}) return { id: memberID, // common unique ID: it can be either the contract ID or the invite key contractID, diff --git a/frontend/views/components/AvatarUser.vue b/frontend/views/components/AvatarUser.vue index 550b367a0e..403cba9f8f 100644 --- a/frontend/views/components/AvatarUser.vue +++ b/frontend/views/components/AvatarUser.vue @@ -18,7 +18,9 @@ export default ({ picture: { type: [String, Object] }, - contractID: String, + contractID: { + type: String + }, alt: { type: String, default: '' diff --git a/shared/domains/chelonia/files.js b/shared/domains/chelonia/files.js index 0da16bc6fa..6820492e19 100644 --- a/shared/domains/chelonia/files.js +++ b/shared/domains/chelonia/files.js @@ -369,26 +369,25 @@ export default (sbp('sbp/selectors/register', { throw new TypeError('A manifest CID must be provided') } if (!Array.isArray(manifestCid)) manifestCid = [manifestCid] - // Validation - manifestCid.forEach((cid) => { + return await Promise.allSettled(manifestCid.map(async (cid) => { const hasCredential = has(credentials, cid) - const hasToken = has(credentials[cid], 'token') - const hasBillableContractID = has(credentials[cid], 'billableContractID') + const hasToken = has(credentials[cid], 'token') && credentials[cid].token + const hasBillableContractID = has(credentials[cid], 'billableContractID') && credentials[cid].billableContractID if (!hasCredential || (!hasToken && hasToken === hasBillableContractID)) { throw new TypeError(`Either a token or a billable contract ID must be provided for ${cid}`) } - }) - return await Promise.all(manifestCid.map(async (cid) => { - const { token, billableContractID } = credentials[cid] + const response = await fetch(`${this.config.connectionURL}/deleteFile/${cid}`, { method: 'POST', signal: this.abortController.signal, headers: new Headers([ ['authorization', - token - ? `bearer ${token}` + hasToken + // $FlowFixMe[incompatible-type] + ? `bearer ${credentials[cid].token}` + // $FlowFixMe[incompatible-type] // $FlowFixMe[incompatible-call] - : buildShelterAuthorizationHeader.call(this, billableContractID)] + : buildShelterAuthorizationHeader.call(this, credentials[cid].billableContractID)] ]) }) if (!response.ok) { diff --git a/shared/domains/chelonia/internals.js b/shared/domains/chelonia/internals.js index f8dcaea788..922e015e6b 100644 --- a/shared/domains/chelonia/internals.js +++ b/shared/domains/chelonia/internals.js @@ -724,7 +724,7 @@ export default (sbp('sbp/selectors/register', { const cheloniaState = sbp(self.config.stateSelector) if (!cheloniaState[v.contractID]) { - config.reactiveSet(cheloniaState, v.contractID, Object.create(null)) + return } const targetState = cheloniaState[v.contractID] diff --git a/shared/domains/chelonia/utils.js b/shared/domains/chelonia/utils.js index 89ec983d27..0356487005 100644 --- a/shared/domains/chelonia/utils.js +++ b/shared/domains/chelonia/utils.js @@ -551,6 +551,11 @@ export const getContractIDfromKeyId = (contractID: string, signingKeyId: ?string } export function eventsAfter (contractID: string, sinceHeight: number, limit?: number, sinceHash?: string): ReadableStream { + if (!contractID) { + // Avoid making a network roundtrip to tell us what we already know + throw new Error('Missing contract ID') + } + const fetchEventsStreamReader = async () => { requestLimit = Math.min(limit ?? MAX_EVENTS_AFTER, remainingEvents) const eventsResponse = await fetch(`${this.config.connectionURL}/eventsAfter/${contractID}/${sinceHeight}${Number.isInteger(requestLimit) ? `/${requestLimit}` : ''}`, { signal })