fix: delete member + content from the admin side

pull/3052/head
Audric Ackermann 1 year ago
parent c476ad1704
commit aa6d39c270

@ -35,6 +35,7 @@ window.sessionFeatureFlags = {
debug: { debug: {
debugLogging: !_.isEmpty(process.env.SESSION_DEBUG), debugLogging: !_.isEmpty(process.env.SESSION_DEBUG),
debugLibsessionDumps: !_.isEmpty(process.env.SESSION_DEBUG_LIBSESSION_DUMPS), debugLibsessionDumps: !_.isEmpty(process.env.SESSION_DEBUG_LIBSESSION_DUMPS),
debugBuiltSnodeRequests: !_.isEmpty(process.env.SESSION_DEBUG_BUILT_SNODE_REQUEST),
debugFileServerRequests: false, debugFileServerRequests: false,
debugNonSnodeRequests: false, debugNonSnodeRequests: false,
debugOnionRequests: false, debugOnionRequests: false,

@ -1,7 +1,5 @@
import { PubkeyType, WithGroupPubkey } from 'libsession_util_nodejs'; import { PubkeyType, WithGroupPubkey } from 'libsession_util_nodejs';
type PrArrayMsgIds = Promise<Array<string>>;
export type DataCallArgs<T extends (args: any) => any> = Parameters<T>[0]; export type DataCallArgs<T extends (args: any) => any> = Parameters<T>[0];
export type DeleteAllMessageFromSendersInConversationType = ( export type DeleteAllMessageFromSendersInConversationType = (
@ -9,14 +7,14 @@ export type DeleteAllMessageFromSendersInConversationType = (
toRemove: Array<PubkeyType>; toRemove: Array<PubkeyType>;
signatureTimestamp: number; signatureTimestamp: number;
} }
) => PrArrayMsgIds; ) => Promise<{ messageHashes: Array<string> }>;
export type DeleteAllMessageHashesInConversationType = ( export type DeleteAllMessageHashesInConversationType = (
args: WithGroupPubkey & { args: WithGroupPubkey & {
messageHashes: Array<string>; messageHashes: Array<string>;
signatureTimestamp: number; signatureTimestamp: number;
} }
) => PrArrayMsgIds; ) => Promise<{ messageHashes: Array<string> }>;
export type DeleteAllMessageHashesInConversationMatchingAuthorType = ( export type DeleteAllMessageHashesInConversationMatchingAuthorType = (
args: WithGroupPubkey & { args: WithGroupPubkey & {

@ -188,6 +188,7 @@ export async function deleteMessagesFromSwarmOnly(
pubkey: PubkeyType | GroupPubkeyType pubkey: PubkeyType | GroupPubkeyType
) { ) {
const deletionMessageHashes = isStringArray(messages) ? messages : getMessageHashes(messages); const deletionMessageHashes = isStringArray(messages) ? messages : getMessageHashes(messages);
try { try {
if (isEmpty(messages)) { if (isEmpty(messages)) {
return false; return false;

@ -1095,14 +1095,17 @@ function deleteAllMessageFromSendersInConversation(
instance?: BetterSqlite3.Database instance?: BetterSqlite3.Database
): AwaitedReturn<DeleteAllMessageFromSendersInConversationType> { ): AwaitedReturn<DeleteAllMessageFromSendersInConversationType> {
if (!groupPk || !toRemove.length) { if (!groupPk || !toRemove.length) {
return []; return { messageHashes: [] };
} }
return assertGlobalInstanceOrInstance(instance) const messageHashes = compact(
.prepare( assertGlobalInstanceOrInstance(instance)
`DELETE FROM ${MESSAGES_TABLE} WHERE conversationId = ? AND sent_at <= ? AND source IN ( ${toRemove.map(() => '?').join(', ')} ) RETURNING id` .prepare(
) `DELETE FROM ${MESSAGES_TABLE} WHERE conversationId = ? AND sent_at <= ? AND source IN ( ${toRemove.map(() => '?').join(', ')} ) RETURNING messageHash`
.all(groupPk, signatureTimestamp, ...toRemove) )
.map(m => m.id); .all(groupPk, signatureTimestamp, ...toRemove)
.map(m => m.messageHash)
);
return { messageHashes };
} }
function deleteAllMessageHashesInConversation( function deleteAllMessageHashesInConversation(
@ -1114,14 +1117,17 @@ function deleteAllMessageHashesInConversation(
instance?: BetterSqlite3.Database instance?: BetterSqlite3.Database
): AwaitedReturn<DeleteAllMessageHashesInConversationType> { ): AwaitedReturn<DeleteAllMessageHashesInConversationType> {
if (!groupPk || !messageHashes.length) { if (!groupPk || !messageHashes.length) {
return []; return { messageHashes: [] };
} }
return assertGlobalInstanceOrInstance(instance) const deletedMessageHashes = compact(
.prepare( assertGlobalInstanceOrInstance(instance)
`DELETE FROM ${MESSAGES_TABLE} WHERE conversationId = ? AND sent_at <= ? AND messageHash IN ( ${messageHashes.map(() => '?').join(', ')} ) RETURNING id` .prepare(
) `DELETE FROM ${MESSAGES_TABLE} WHERE conversationId = ? AND sent_at <= ? AND messageHash IN ( ${messageHashes.map(() => '?').join(', ')} ) RETURNING messageHash`
.all(groupPk, signatureTimestamp, ...messageHashes) )
.map(m => m.id); .all(groupPk, signatureTimestamp, ...messageHashes)
.map(m => m.messageHash)
);
return { messageHashes: deletedMessageHashes };
} }
function deleteAllMessageHashesInConversationMatchingAuthor( function deleteAllMessageHashesInConversationMatchingAuthor(

@ -1,5 +1,5 @@
import { GroupPubkeyType, PubkeyType, WithGroupPubkey } from 'libsession_util_nodejs'; import { GroupPubkeyType, PubkeyType, WithGroupPubkey } from 'libsession_util_nodejs';
import { isEmpty, isFinite, isNumber } from 'lodash'; import { compact, isEmpty, isFinite, isNumber } from 'lodash';
import { Data } from '../../data/data'; import { Data } from '../../data/data';
import { deleteMessagesFromSwarmOnly } from '../../interactions/conversations/unsendingInteractions'; import { deleteMessagesFromSwarmOnly } from '../../interactions/conversations/unsendingInteractions';
import { ConversationTypeEnum } from '../../models/conversationAttributes'; import { ConversationTypeEnum } from '../../models/conversationAttributes';
@ -21,7 +21,7 @@ import { PreConditionFailed } from '../../session/utils/errors';
import { UserSync } from '../../session/utils/job_runners/jobs/UserSyncJob'; import { UserSync } from '../../session/utils/job_runners/jobs/UserSyncJob';
import { LibSessionUtil } from '../../session/utils/libsession/libsession_utils'; import { LibSessionUtil } from '../../session/utils/libsession/libsession_utils';
import { SessionUtilConvoInfoVolatile } from '../../session/utils/libsession/libsession_utils_convo_info_volatile'; import { SessionUtilConvoInfoVolatile } from '../../session/utils/libsession/libsession_utils_convo_info_volatile';
import { messagesExpired } from '../../state/ducks/conversations'; import { messageHashesExpired, messagesExpired } from '../../state/ducks/conversations';
import { groupInfoActions } from '../../state/ducks/metaGroups'; import { groupInfoActions } from '../../state/ducks/metaGroups';
import { toFixedUint8ArrayOfLength } from '../../types/sqlSharedTypes'; import { toFixedUint8ArrayOfLength } from '../../types/sqlSharedTypes';
import { BlockedNumberController } from '../../util'; import { BlockedNumberController } from '../../util';
@ -460,10 +460,10 @@ async function handleGroupDeleteMemberContentMessage({
}); // this is step 3. }); // this is step 3.
window.inboxStore.dispatch( window.inboxStore.dispatch(
messagesExpired( messageHashesExpired(
[...deletedByHashes, ...deletedBySenders].map(m => ({ compact([...deletedByHashes.messageHashes, ...deletedBySenders.messageHashes]).map(m => ({
conversationKey: groupPk, conversationKey: groupPk,
messageId: m, messageHash: m,
})) }))
) )
); );

@ -565,7 +565,7 @@ export class SwarmPolling {
} }
if (type === ConversationTypeEnum.GROUPV2 && PubKey.is03Pubkey(pubkey)) { if (type === ConversationTypeEnum.GROUPV2 && PubKey.is03Pubkey(pubkey)) {
const toBump = await MetaGroupWrapperActions.currentHashes(pubkey); const toBump = await MetaGroupWrapperActions.currentHashes(pubkey);
window.log.debug(`configHashesToBump group count: ${toBump.length}`); window.log.debug(`configHashesToBump group(${ed25519Str(pubkey)}) count: ${toBump.length}`);
return toBump; return toBump;
} }
return []; return [];

@ -56,6 +56,7 @@ import { UserUtils } from '../utils';
import { ed25519Str, fromUInt8ArrayToBase64 } from '../utils/String'; import { ed25519Str, fromUInt8ArrayToBase64 } from '../utils/String';
import { MessageSentHandler } from './MessageSentHandler'; import { MessageSentHandler } from './MessageSentHandler';
import { MessageWrapper } from './MessageWrapper'; import { MessageWrapper } from './MessageWrapper';
import { stringify } from '../../types/sqlSharedTypes';
// ================ SNODE STORE ================ // ================ SNODE STORE ================
@ -347,6 +348,19 @@ async function getSignatureParamsFromNamespace(
return {}; return {};
} }
function logBuildSubRequests(subRequests: Array<BuiltSnodeSubRequests>) {
if (!window.sessionFeatureFlags.debug.debugBuiltSnodeRequests) {
return;
}
window.log.debug(
`\n========================================\nsubRequests: [\n\t${subRequests
.map(m => {
return stringify(m);
})
.join(',\n\t')}]\n========================================`
);
}
async function signSubRequests( async function signSubRequests(
params: Array<RawSnodeSubRequests> params: Array<RawSnodeSubRequests>
): Promise<Array<BuiltSnodeSubRequests>> { ): Promise<Array<BuiltSnodeSubRequests>> {
@ -356,6 +370,8 @@ async function signSubRequests(
}) })
); );
logBuildSubRequests(signedRequests);
return signedRequests; return signedRequests;
} }

@ -5,6 +5,7 @@ import { v4 } from 'uuid';
import { StringUtils } from '../..'; import { StringUtils } from '../..';
import { Data } from '../../../../data/data'; import { Data } from '../../../../data/data';
import { deleteMessagesFromSwarmOnly } from '../../../../interactions/conversations/unsendingInteractions'; import { deleteMessagesFromSwarmOnly } from '../../../../interactions/conversations/unsendingInteractions';
import { messageHashesExpired } from '../../../../state/ducks/conversations';
import { import {
MetaGroupWrapperActions, MetaGroupWrapperActions,
MultiEncryptWrapperActions, MultiEncryptWrapperActions,
@ -217,8 +218,21 @@ class GroupPendingRemovalsJob extends PersistedJob<GroupPendingRemovalsPersisted
signatureTimestamp: GetNetworkTime.now(), signatureTimestamp: GetNetworkTime.now(),
}); });
if (msgHashesToDeleteOnGroupSwarm.length) { if (msgHashesToDeleteOnGroupSwarm.messageHashes.length) {
await deleteMessagesFromSwarmOnly(msgHashesToDeleteOnGroupSwarm, groupPk); const deleted = await deleteMessagesFromSwarmOnly(
msgHashesToDeleteOnGroupSwarm.messageHashes,
groupPk
);
if (deleted) {
window.inboxStore.dispatch(
messageHashesExpired(
msgHashesToDeleteOnGroupSwarm.messageHashes.map(messageHash => ({
conversationKey: groupPk,
messageHash,
}))
)
);
}
} }
} }
} catch (e) { } catch (e) {

@ -11,6 +11,7 @@ import {
} from '../../../../webworker/workers/browser/libsession_worker_interface'; } from '../../../../webworker/workers/browser/libsession_worker_interface';
import { import {
DeleteAllFromGroupMsgNodeSubRequest, DeleteAllFromGroupMsgNodeSubRequest,
DeleteHashesFromGroupNodeSubRequest,
StoreGroupKeysSubRequest, StoreGroupKeysSubRequest,
StoreGroupMessageSubRequest, StoreGroupMessageSubRequest,
SubaccountRevokeSubRequest, SubaccountRevokeSubRequest,
@ -144,27 +145,30 @@ async function pushChangesToGroupSwarmIfNeeded({
m => m =>
m instanceof SubaccountRevokeSubRequest || m instanceof SubaccountRevokeSubRequest ||
m instanceof SubaccountUnrevokeSubRequest || m instanceof SubaccountUnrevokeSubRequest ||
m instanceof DeleteAllFromGroupMsgNodeSubRequest m instanceof DeleteAllFromGroupMsgNodeSubRequest ||
m instanceof DeleteHashesFromGroupNodeSubRequest
); );
const sortedSubRequests = compact([
supplementalKeysSubRequest, // this needs to be stored first
...pendingConfigRequests, // groupKeys are first in this array, so all good, then groupInfos are next
...extraStoreRequests, // this can be stored anytime
...extraRequests,
]);
const result = await MessageSender.sendEncryptedDataToSnode({ const result = await MessageSender.sendEncryptedDataToSnode({
// Note: this is on purpose that supplementalKeysSubRequest is before pendingConfigRequests // Note: this is on purpose that supplementalKeysSubRequest is before pendingConfigRequests
// as this is to avoid a race condition where a device is polling right // as this is to avoid a race condition where a device is polling right
// while we are posting the configs (already encrypted with the new keys) // while we are posting the configs (already encrypted with the new keys)
sortedSubRequests: compact([ sortedSubRequests,
supplementalKeysSubRequest, // this needs to be stored first
...pendingConfigRequests, // groupKeys are first in this array, so all good, then groupInfos are next
...extraStoreRequests, // this can be stored anytime
...extraRequests,
]),
destination: groupPk, destination: groupPk,
method: 'sequence', method: 'sequence',
}); });
const expectedReplyLength = const expectedReplyLength =
pendingConfigRequests.length + // each of those are sent as a subrequest
(supplementalKeysSubRequest ? 1 : 0) + // we are sending all the supplemental keys as a single subrequest (supplementalKeysSubRequest ? 1 : 0) + // we are sending all the supplemental keys as a single subrequest
(extraStoreRequests ? 1 : 0) + // each of those are sent as a subrequest pendingConfigRequests.length + // each of those are sent as a subrequest
extraStoreRequests.length + // each of those are sent as a subrequest
extraRequestWithExpectedResults.length; // each of those are sent as a subrequest, but they don't all return something... extraRequestWithExpectedResults.length; // each of those are sent as a subrequest, but they don't all return something...
// we do a sequence call here. If we do not have the right expected number of results, consider it a failure // we do a sequence call here. If we do not have the right expected number of results, consider it a failure

@ -601,18 +601,32 @@ function handleMessagesChangedOrAdded(
function handleMessageExpiredOrDeleted( function handleMessageExpiredOrDeleted(
state: ConversationsStateType, state: ConversationsStateType,
payload: { payload: { conversationKey: string } & (
messageId: string; | {
conversationKey: string; messageId: string;
} }
| {
messageHash: string;
}
)
) { ) {
const { conversationKey, messageId } = payload; const { conversationKey } = payload;
const messageId = (payload as any).messageId as string | undefined;
const messageHash = (payload as any).messageHash as string | undefined;
if (conversationKey === state.selectedConversation) { if (conversationKey === state.selectedConversation) {
// search if we find this message id. // search if we find this message id.
// we might have not loaded yet, so this case might not happen // we might have not loaded yet, so this case might not happen
const messageInStoreIndex = state?.messages.findIndex(m => m.propsForMessage.id === messageId); const messageInStoreIndex = state?.messages.findIndex(
m =>
(messageId && m.propsForMessage.id === messageId) ||
(messageHash && m.propsForMessage.messageHash === messageHash)
);
const editedQuotes = { ...state.quotes }; const editedQuotes = { ...state.quotes };
if (messageInStoreIndex >= 0) { if (messageInStoreIndex >= 0) {
const msgToRemove = state.messages[messageInStoreIndex];
const extractedMessageId = msgToRemove.propsForMessage.id;
// we cannot edit the array directly, so slice the first part, and slice the second part, // we cannot edit the array directly, so slice the first part, and slice the second part,
// keeping the index removed out // keeping the index removed out
const editedMessages = [ const editedMessages = [
@ -637,7 +651,9 @@ function handleMessageExpiredOrDeleted(
messages: editedMessages, messages: editedMessages,
quotes: editedQuotes, quotes: editedQuotes,
firstUnreadMessageId: firstUnreadMessageId:
state.firstUnreadMessageId === messageId ? undefined : state.firstUnreadMessageId, state.firstUnreadMessageId === extractedMessageId
? undefined
: state.firstUnreadMessageId,
}; };
} }
@ -649,10 +665,16 @@ function handleMessageExpiredOrDeleted(
function handleMessagesExpiredOrDeleted( function handleMessagesExpiredOrDeleted(
state: ConversationsStateType, state: ConversationsStateType,
action: PayloadAction< action: PayloadAction<
Array<{ Array<
messageId: string; { conversationKey: string } & (
conversationKey: string; | {
}> messageId: string;
}
| {
messageHash: string;
}
)
>
> >
): ConversationsStateType { ): ConversationsStateType {
let stateCopy = state; let stateCopy = state;
@ -797,6 +819,17 @@ const conversationsSlice = createSlice({
) { ) {
return handleMessagesExpiredOrDeleted(state, action); return handleMessagesExpiredOrDeleted(state, action);
}, },
messageHashesExpired(
state: ConversationsStateType,
action: PayloadAction<
Array<{
messageHash: string;
conversationKey: string;
}>
>
) {
return handleMessagesExpiredOrDeleted(state, action);
},
messagesDeleted( messagesDeleted(
state: ConversationsStateType, state: ConversationsStateType,
@ -1139,6 +1172,7 @@ export const {
conversationRemoved, conversationRemoved,
removeAllConversations, removeAllConversations,
messagesExpired, messagesExpired,
messageHashesExpired,
messagesDeleted, messagesDeleted,
conversationReset, conversationReset,
messagesChanged, messagesChanged,

@ -308,11 +308,15 @@ export function toFixedUint8ArrayOfLength<T extends number>(
} }
export function stringify(obj: unknown) { export function stringify(obj: unknown) {
return JSON.stringify(obj, (_key, value) => { return JSON.stringify(
return value instanceof Uint8Array obj,
? `Uint8Array(${value.length}): ${toHex(value)}` (_key, value) => {
: value?.type === 'Buffer' && value?.data return value instanceof Uint8Array
? `Buffer: ${toHex(value.data)}` ? `Uint8Array(${value.length}): ${toHex(value)}`
: value; : value?.type === 'Buffer' && value?.data
}); ? `Buffer: ${toHex(value.data)}`
: value;
},
2
);
} }

1
ts/window.d.ts vendored

@ -34,6 +34,7 @@ declare global {
debug: { debug: {
debugLogging: boolean; debugLogging: boolean;
debugLibsessionDumps: boolean; debugLibsessionDumps: boolean;
debugBuiltSnodeRequests: boolean;
debugFileServerRequests: boolean; debugFileServerRequests: boolean;
debugNonSnodeRequests: boolean; debugNonSnodeRequests: boolean;
debugOnionRequests: boolean; debugOnionRequests: boolean;

Loading…
Cancel
Save