diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index 4553890c1..bf0f7a21b 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -29,6 +29,7 @@ import { ConversationController } from '../../../session/conversations'; import { getMessageById, getPubkeysInPublicConversation } from '../../../data/data'; import autoBind from 'auto-bind'; import { getDecryptedMediaUrl } from '../../../session/crypto/DecryptedAttachmentsManager'; +import { deleteOpenGroupMessages } from '../../../interactions/conversation'; interface State { // Message sending progress @@ -587,8 +588,6 @@ export class SessionConversation extends React.Component { const doDelete = async () => { let toDeleteLocally; - // VINCE TODO: MARK TO-DELETE MESSAGES AS READ - if (selectedConversation.isPublic) { // Get our Moderator status const ourDevicePubkey = UserUtils.getOurPubKeyStrFromCache(); @@ -608,7 +607,7 @@ export class SessionConversation extends React.Component { return; } - toDeleteLocally = await conversationModel.deletePublicMessages(selectedMessages); + toDeleteLocally = await deleteOpenGroupMessages(selectedMessages, conversationModel); if (toDeleteLocally.length === 0) { // Message failed to delete from server, show error? return; @@ -618,7 +617,7 @@ export class SessionConversation extends React.Component { } await Promise.all( - toDeleteLocally.map(async (message: any) => { + toDeleteLocally.map(async message => { await conversationModel.removeMessage(message.id); }) ); diff --git a/ts/data/opengroups.ts b/ts/data/opengroups.ts index 7414e7662..6a85d856a 100644 --- a/ts/data/opengroups.ts +++ b/ts/data/opengroups.ts @@ -11,6 +11,9 @@ export type OpenGroupV2Room = { imageID?: string; // the url to the group's image conversationId?: string; // the linked ConversationModel.id lastMessageFetchedServerID?: number; + /** + * This value represents the rowId of the last message deleted. Not the id of the last message ID + */ lastMessageDeletedServerID?: number; token?: string; // currently, the token is on a per room basis }; diff --git a/ts/interactions/conversation.ts b/ts/interactions/conversation.ts index f2ec6d676..03ba45c82 100644 --- a/ts/interactions/conversation.ts +++ b/ts/interactions/conversation.ts @@ -5,6 +5,11 @@ import { } from '../opengroup/utils/OpenGroupUtils'; import { getV2OpenGroupRoom } from '../data/opengroups'; import { ToastUtils } from '../session/utils'; +import { ConversationModel } from '../models/conversation'; +import { MessageModel } from '../models/message'; +import { ApiV2 } from '../opengroup/opengroupV2'; + +import _ from 'lodash'; export async function copyPublicKey(convoId: string) { if (convoId.match(openGroupPrefixRegex)) { @@ -35,3 +40,81 @@ export async function copyPublicKey(convoId: string) { ToastUtils.pushCopiedToClipBoard(); } + +export async function deleteOpenGroupMessages( + messages: Array, + convo: ConversationModel +): Promise> { + if (!convo.isPublic()) { + throw new Error('cannot delete public message on a non public groups'); + } + + if (convo.isOpenGroupV2()) { + const roomInfos = convo.toOpenGroupV2(); + // on v2 servers we can only remove a single message per request.. + // so logic here is to delete each messages and get which one where not removed + const allDeletedResults = await Promise.all( + messages.map(async msg => { + const msgId = msg.get('serverId'); + if (msgId) { + const isRemovedOnServer = await ApiV2.deleteSingleMessage(msgId, roomInfos); + return { message: msg, isRemovedOnServer }; + } else { + window.log.warn('serverId not valid for deletePublicMessage'); + return { message: msg, isRemovedOnServer: false }; + } + }) + ); + if (allDeletedResults.every(m => m.isRemovedOnServer)) { + window.log.info('all those serverIds where removed'); + } else { + if (allDeletedResults.some(m => m.isRemovedOnServer)) { + window.log.info('some of those serverIds where not removed'); + } else { + window.log.info('failed to remove all those serverIds message'); + } + } + // remove only the messag we managed to remove on the server + const msgToDeleteLocally = allDeletedResults + .filter(m => m.isRemovedOnServer) + .map(m => m.message); + return msgToDeleteLocally; + } else if (convo.isOpenGroupV1()) { + const channelAPI = await convo.getPublicSendData(); + + if (!channelAPI) { + throw new Error('Unable to get public channel API'); + } + + const invalidMessages = messages.filter(m => !m.attributes.serverId); + const pendingMessages = messages.filter(m => m.attributes.serverId); + + let deletedServerIds = []; + let ignoredServerIds = []; + + if (pendingMessages.length > 0) { + const result = await channelAPI.deleteMessages( + pendingMessages.map(m => m.attributes.serverId) + ); + deletedServerIds = result.deletedIds; + ignoredServerIds = result.ignoredIds; + } + + const toDeleteLocallyServerIds = _.union(deletedServerIds, ignoredServerIds); + let toDeleteLocally = messages.filter(m => + toDeleteLocallyServerIds.includes(m.attributes.serverId) + ); + toDeleteLocally = _.union(toDeleteLocally, invalidMessages); + + await Promise.all( + toDeleteLocally.map(async m => { + await convo.removeMessage(m.id); + }) + ); + + await convo.updateLastMessage(); + + return toDeleteLocally; + } + return []; +} diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 190434a65..2fdf70701 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -4,7 +4,7 @@ import { getMessageQueue } from '../session'; import { ConversationController } from '../session/conversations'; import { ClosedGroupVisibleMessage } from '../session/messages/outgoing/visibleMessage/ClosedGroupVisibleMessage'; import { PubKey } from '../session/types'; -import { UserUtils } from '../session/utils'; +import { ToastUtils, UserUtils } from '../session/utils'; import { BlockedNumberController } from '../util'; import { MessageController } from '../session/messages'; import { leaveClosedGroup } from '../session/group'; @@ -43,6 +43,7 @@ import { getV2OpenGroupRoom } from '../data/opengroups'; import { OpenGroupVisibleMessage } from '../session/messages/outgoing/visibleMessage/OpenGroupVisibleMessage'; import { OpenGroupRequestCommonType } from '../opengroup/opengroupV2/ApiUtil'; import { getOpenGroupV2FromConversationId } from '../opengroup/utils/OpenGroupUtils'; +import { ApiV2 } from '../opengroup/opengroupV2'; export enum ConversationType { GROUP = 'group', @@ -1324,48 +1325,6 @@ export class ConversationModel extends Backbone.Model { }); } - public async deletePublicMessages(messages: Array) { - if (this.isOpenGroupV2()) { - console.warn('FIXME deletePublicMessages'); - throw new Error('deletePublicMessages todo'); - } - const channelAPI = await this.getPublicSendData(); - - if (!channelAPI) { - throw new Error('Unable to get public channel API'); - } - - const invalidMessages = messages.filter(m => !m.attributes.serverId); - const pendingMessages = messages.filter(m => m.attributes.serverId); - - let deletedServerIds = []; - let ignoredServerIds = []; - - if (pendingMessages.length > 0) { - const result = await channelAPI.deleteMessages( - pendingMessages.map(m => m.attributes.serverId) - ); - deletedServerIds = result.deletedIds; - ignoredServerIds = result.ignoredIds; - } - - const toDeleteLocallyServerIds = _.union(deletedServerIds, ignoredServerIds); - let toDeleteLocally = messages.filter(m => - toDeleteLocallyServerIds.includes(m.attributes.serverId) - ); - toDeleteLocally = _.union(toDeleteLocally, invalidMessages); - - await Promise.all( - toDeleteLocally.map(async m => { - await this.removeMessage(m.id); - }) - ); - - await this.updateLastMessage(); - - return toDeleteLocally; - } - public async removeMessage(messageId: any) { await dataRemoveMessage(messageId); this.updateLastMessage(); diff --git a/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts b/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts index 0f96551a1..0aa0310e2 100644 --- a/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts +++ b/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts @@ -402,6 +402,23 @@ export const unbanUser = async ( return isOk; }; +export const deleteSingleMessage = async ( + messageServerId: number, + roomInfos: OpenGroupRequestCommonType +): Promise => { + const request: OpenGroupV2Request = { + method: 'DELETE', + room: roomInfos.roomId, + server: roomInfos.serverUrl, + isAuthRequired: true, + endpoint: `messages/${messageServerId}`, + }; + const messageDeletedResult = await sendOpenGroupV2Request(request); + const isOk = parseStatusCodeFromOnionRequest(messageDeletedResult) === 200; + console.warn('messageDeletedResult', messageDeletedResult); + return isOk; +}; + export const getAllRoomInfos = async (roomInfos: OpenGroupRequestCommonType) => { // room should not be required here const request: OpenGroupV2Request = { diff --git a/ts/opengroup/opengroupV2/OpenGroupAPIV2CompactPoll.ts b/ts/opengroup/opengroupV2/OpenGroupAPIV2CompactPoll.ts index 6a9aeb657..0fd0039c9 100644 --- a/ts/opengroup/opengroupV2/OpenGroupAPIV2CompactPoll.ts +++ b/ts/opengroup/opengroupV2/OpenGroupAPIV2CompactPoll.ts @@ -57,13 +57,13 @@ const getCompactPollRequest = async ( room_id: roomId, auth_token: token || '', }; - if (lastMessageDeletedServerID) { - roomRequestContent.from_deletion_server_id = lastMessageDeletedServerID; - } - - if (lastMessageFetchedServerID) { - roomRequestContent.from_message_server_id = lastMessageFetchedServerID; - } + // if (lastMessageDeletedServerID) { + roomRequestContent.from_deletion_server_id = lastMessageDeletedServerID; + // } + // if (lastMessageFetchedServerID) { + roomRequestContent.from_message_server_id = lastMessageFetchedServerID || 1; + // } + console.warn('compactPoll, ', roomRequestContent); return roomRequestContent; } catch (e) { @@ -161,9 +161,11 @@ async function sendOpenGroupV2RequestCompactPoll( return roomPollValidResults; } +export type ParsedDeletions = Array<{ id: number; deleted_message_id: number }>; + export type ParsedRoomCompactPollResults = { roomId: string; - deletions: Array; + deletions: ParsedDeletions; messages: Array; moderators: Array; statusCode: number; @@ -194,7 +196,7 @@ const parseCompactPollResult = async ( const validMessages = await parseMessages(rawMessages); const moderators = rawMods.sort() as Array; - const deletions = rawDeletions as Array; + const deletions = rawDeletions as ParsedDeletions; const statusCode = rawStatusCode as number; return { diff --git a/ts/opengroup/opengroupV2/OpenGroupServerPoller.ts b/ts/opengroup/opengroupV2/OpenGroupServerPoller.ts index a6bfa19e7..73fb4d04d 100644 --- a/ts/opengroup/opengroupV2/OpenGroupServerPoller.ts +++ b/ts/opengroup/opengroupV2/OpenGroupServerPoller.ts @@ -2,7 +2,11 @@ import { AbortController } from 'abort-controller'; import { ConversationController } from '../../session/conversations'; import { getOpenGroupV2ConversationId } from '../utils/OpenGroupUtils'; import { OpenGroupRequestCommonType } from './ApiUtil'; -import { compactFetchEverything, ParsedRoomCompactPollResults } from './OpenGroupAPIV2CompactPoll'; +import { + compactFetchEverything, + ParsedDeletions, + ParsedRoomCompactPollResults, +} from './OpenGroupAPIV2CompactPoll'; import _ from 'lodash'; import { ConversationModel } from '../../models/conversation'; import { getMessageIdsFromServerIds, removeMessage } from '../../data/data'; @@ -10,7 +14,7 @@ import { getV2OpenGroupRoom, saveV2OpenGroupRoom } from '../../data/opengroups'; import { OpenGroupMessageV2 } from './OpenGroupMessageV2'; import { handleOpenGroupV2Message } from '../../receiver/receiver'; -const pollForEverythingInterval = 4 * 1000; +const pollForEverythingInterval = 8 * 1000; /** * An OpenGroupServerPollerV2 polls for everything for a particular server. We should @@ -165,33 +169,40 @@ export class OpenGroupServerPoller { } const handleDeletions = async ( - deletedIds: Array, + deleted: ParsedDeletions, conversationId: string, convo?: ConversationModel ) => { + const allIdsRemoved = (deleted || []).map(d => d.deleted_message_id); + const allRowIds = (deleted || []).map(d => d.id); + const maxDeletedId = Math.max(...allRowIds); try { - const maxDeletedId = Math.max(...deletedIds); - const messageIds = await getMessageIdsFromServerIds(deletedIds, conversationId); - if (!messageIds?.length) { - return; - } - const roomInfos = await getV2OpenGroupRoom(conversationId); - if (roomInfos && roomInfos.lastMessageDeletedServerID !== maxDeletedId) { - roomInfos.lastMessageDeletedServerID = maxDeletedId; - await saveV2OpenGroupRoom(roomInfos); - } + console.warn('We got deletion to do:', deleted, maxDeletedId); + + const messageIds = await getMessageIdsFromServerIds(allIdsRemoved, conversationId); await Promise.all( - messageIds.map(async id => { + (messageIds || []).map(async id => { if (convo) { await convo.removeMessage(id); } await removeMessage(id); }) ); - // we want to try to update our lastDeletedId + // } catch (e) { window.log.warn('handleDeletions failed:', e); + } finally { + try { + const roomInfos = await getV2OpenGroupRoom(conversationId); + + if (roomInfos && roomInfos.lastMessageDeletedServerID !== maxDeletedId) { + roomInfos.lastMessageDeletedServerID = maxDeletedId; + await saveV2OpenGroupRoom(roomInfos); + } + } catch (e) { + window.log.warn('handleDeletions updating roomInfos failed:', e); + } } }; @@ -234,6 +245,7 @@ const handleCompactPollResults = async ( serverUrl: string, results: Array ) => { + console.warn('compoll res', results); await Promise.all( results.map(async res => { const convoId = getOpenGroupV2ConversationId(serverUrl, res.roomId); @@ -241,6 +253,7 @@ const handleCompactPollResults = async ( // we want to do deletions even if we somehow lost the convo. if (res.deletions.length) { + console.warn('res.deletions', res.deletions); // new deletions await handleDeletions(res.deletions, convoId, convo); }