feat: delete msg on swarm when admin receives member request

pull/3052/head
Audric Ackermann 12 months ago
parent 2ec6c7f29c
commit 0a3d71fe03

@ -24,4 +24,4 @@ export type DeleteAllMessageHashesInConversationMatchingAuthorType = (
author: PubkeyType; author: PubkeyType;
signatureTimestamp: number; signatureTimestamp: number;
} }
) => PrArrayMsgIds; ) => Promise<{ msgIdsDeleted: Array<string>; msgHashesDeleted: Array<string> }>;

@ -126,7 +126,7 @@ async function unsendMessagesForEveryone(
await unsendMessagesForEveryone1o1AndLegacy(conversation, conversation.id, msgsToDelete); await unsendMessagesForEveryone1o1AndLegacy(conversation, conversation.id, msgsToDelete);
} else if (conversation.isClosedGroupV2()) { } else if (conversation.isClosedGroupV2()) {
if (!PubKey.is03Pubkey(destinationId)) { if (!PubKey.is03Pubkey(destinationId)) {
throw new Error('invalid conversation id (03) for unsendMessageForEveryone'); throw new Error('invalid conversation id (03) for unsendMessageForEveryone');
} }
await unsendMessagesForEveryoneGroupV2({ await unsendMessagesForEveryoneGroupV2({
groupPk: destinationId, groupPk: destinationId,
@ -199,11 +199,10 @@ export async function deleteMessagesFromSwarmOnly(
); );
return false; return false;
} }
const errorOnAtLeastOneSnode = await SnodeAPI.networkDeleteMessages( if (PubKey.is03Pubkey(pubkey)) {
deletionMessageHashes, return await SnodeAPI.networkDeleteMessagesForGroup(deletionMessageHashes, pubkey);
pubkey }
); return await SnodeAPI.networkDeleteMessageOurSwarm(deletionMessageHashes, pubkey);
return errorOnAtLeastOneSnode;
} catch (e) { } catch (e) {
window.log?.error( window.log?.error(
`deleteMessagesFromSwarmOnly: Error deleting message from swarm of ${ed25519Str(pubkey)}, hashes: ${deletionMessageHashes}`, `deleteMessagesFromSwarmOnly: Error deleting message from swarm of ${ed25519Str(pubkey)}, hashes: ${deletionMessageHashes}`,

@ -1134,14 +1134,18 @@ function deleteAllMessageHashesInConversationMatchingAuthor(
instance?: BetterSqlite3.Database instance?: BetterSqlite3.Database
): AwaitedReturn<DeleteAllMessageHashesInConversationMatchingAuthorType> { ): AwaitedReturn<DeleteAllMessageHashesInConversationMatchingAuthorType> {
if (!groupPk || !author || !messageHashes.length) { if (!groupPk || !author || !messageHashes.length) {
return []; return { msgHashesDeleted: [], msgIdsDeleted: [] };
} }
return assertGlobalInstanceOrInstance(instance) const results = assertGlobalInstanceOrInstance(instance)
.prepare( .prepare(
`DELETE FROM ${MESSAGES_TABLE} WHERE conversationId = ? AND source = ? AND sent_at <= ? AND messageHash IN ( ${messageHashes.map(() => '?').join(', ')} ) RETURNING id` `DELETE FROM ${MESSAGES_TABLE} WHERE conversationId = ? AND source = ? AND sent_at <= ? AND messageHash IN ( ${messageHashes.map(() => '?').join(', ')} ) RETURNING id, messageHash;`
) )
.all(groupPk, author, signatureTimestamp, ...messageHashes) .all(groupPk, author, signatureTimestamp, ...messageHashes);
.map(m => m.id);
return {
msgHashesDeleted: results.map(m => m.messageHash),
msgIdsDeleted: results.map(m => m.id),
};
} }
function cleanUpExpirationTimerUpdateHistory( function cleanUpExpirationTimerUpdateHistory(

@ -1,6 +1,7 @@
import { GroupPubkeyType, PubkeyType, WithGroupPubkey } from 'libsession_util_nodejs'; import { GroupPubkeyType, PubkeyType, WithGroupPubkey } from 'libsession_util_nodejs';
import { isEmpty, isFinite, isNumber } from 'lodash'; import { isEmpty, isFinite, isNumber } from 'lodash';
import { Data } from '../../data/data'; import { Data } from '../../data/data';
import { deleteMessagesFromSwarmOnly } from '../../interactions/conversations/unsendingInteractions';
import { ConversationTypeEnum } from '../../models/conversationAttributes'; import { ConversationTypeEnum } from '../../models/conversationAttributes';
import { HexString } from '../../node/hexStrings'; import { HexString } from '../../node/hexStrings';
import { SignalService } from '../../protobuf'; import { SignalService } from '../../protobuf';
@ -377,16 +378,27 @@ async function handleGroupDeleteMemberContentMessage({
if (isEmpty(change.adminSignature)) { if (isEmpty(change.adminSignature)) {
// this is step 1. // this is step 1.
const msgsDeleted = await Data.deleteAllMessageHashesInConversationMatchingAuthor({ const { msgIdsDeleted, msgHashesDeleted } =
author, await Data.deleteAllMessageHashesInConversationMatchingAuthor({
groupPk, author,
messageHashes: change.messageHashes, groupPk,
signatureTimestamp, messageHashes: change.messageHashes,
}); signatureTimestamp,
});
window.inboxStore.dispatch( window.inboxStore.dispatch(
messagesExpired(msgsDeleted.map(m => ({ conversationKey: groupPk, messageId: m }))) messagesExpired(msgIdsDeleted.map(m => ({ conversationKey: groupPk, messageId: m })))
); );
if (msgIdsDeleted.length) {
// Note: we `void` it because we don't want to hang while
// processing the handleGroupDeleteMemberContentMessage itself
// (we are running on the receiving pipeline here)
void deleteMessagesFromSwarmOnly(msgHashesDeleted, groupPk).catch(e => {
// we retry a bunch of times already, so if it still fails, there is not much we can do.
window.log.warn('deleteMessagesFromSwarmOnly failed with', e.message);
});
}
convo.updateLastMessage(); convo.updateLastMessage();
return; return;
} }

@ -14,7 +14,7 @@ import { BatchRequests } from './batchRequest';
import { SnodePool } from './snodePool'; import { SnodePool } from './snodePool';
import { StringUtils, UserUtils } from '../../utils'; import { StringUtils, UserUtils } from '../../utils';
import { ed25519Str, fromBase64ToArray, fromHexToArray } from '../../utils/String'; import { ed25519Str, fromBase64ToArray, fromHexToArray } from '../../utils/String';
import { UserGroupsWrapperActions } from '../../../webworker/workers/browser/libsession_worker_interface';
export const ERROR_CODE_NO_CONNECT = 'ENETUNREACH: No network connection.'; export const ERROR_CODE_NO_CONNECT = 'ENETUNREACH: No network connection.';
@ -30,124 +30,111 @@ const forceNetworkDeletion = async (): Promise<Array<string> | null> => {
async () => { async () => {
const snodeToMakeRequestTo = await SnodePool.getNodeFromSwarmOrThrow(usPk); const snodeToMakeRequestTo = await SnodePool.getNodeFromSwarmOrThrow(usPk);
const builtRequest = await request.buildAndSignParameters(); // we need the timestamp to verify the signature below const builtRequest = await request.buildAndSignParameters(); // we need the timestamp to verify the signature below
return pRetry( const ret = await BatchRequests.doSnodeBatchRequestNoRetries(
async () => { [builtRequest],
const ret = await BatchRequests.doSnodeBatchRequestNoRetries( snodeToMakeRequestTo,
[builtRequest], 10000,
snodeToMakeRequestTo, usPk,
10000, false
usPk, );
false
);
if (!ret || !ret?.[0].body || ret[0].code !== 200) { if (!ret || !ret?.[0].body || ret[0].code !== 200) {
throw new Error( throw new Error(
`Empty response got for ${request.method} on snode ${ed25519Str( `Empty response got for ${request.method} on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519 snodeToMakeRequestTo.pubkey_ed25519
)}` )}`
); );
} }
try {
const firstResultParsedBody = ret[0].body;
const { swarm } = firstResultParsedBody;
if (!swarm) {
throw new Error(
`Invalid JSON swarm response got for ${request.method} on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}, ${firstResultParsedBody}`
);
}
const swarmAsArray = Object.entries(swarm) as Array<Array<any>>;
if (!swarmAsArray.length) {
throw new Error(
`Invalid JSON swarmAsArray response got for ${request.method} on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}, ${firstResultParsedBody}`
);
}
// results will only contains the snode pubkeys which returned invalid/empty results
const results: Array<string> = compact(
swarmAsArray.map(snode => {
const snodePubkey = snode[0];
const snodeJson = snode[1];
const isFailed = snodeJson.failed || false;
if (isFailed) {
const reason = snodeJson.reason;
const statusCode = snodeJson.code;
if (reason && statusCode) {
window?.log?.warn(
`Could not ${request.method} from ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)} due to error: ${reason}: ${statusCode}`
);
// if we tried to make the delete on a snode not in our swarm, just trigger a pRetry error so the outer block here finds new snodes to make the request to.
if (statusCode === 421) {
throw new pRetry.AbortError(
`421 error on network ${request.method}. Retrying with a new snode`
);
}
} else {
window?.log?.warn(
`Could not ${request.method} from ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}`
);
}
return snodePubkey;
}
const deletedObj = snodeJson.deleted as Record<number, Array<string>>; try {
const hashes: Array<string> = []; const firstResultParsedBody = ret[0].body;
const { swarm } = firstResultParsedBody;
for (const key in deletedObj) { if (!swarm) {
if (deletedObj.hasOwnProperty(key)) { throw new Error(
hashes.push(...deletedObj[key]); `Invalid JSON swarm response got for ${request.method} on snode ${ed25519Str(
} snodeToMakeRequestTo.pubkey_ed25519
} )}, ${firstResultParsedBody}`
const sortedHashes = hashes.sort(); );
const signatureSnode = snodeJson.signature as string; }
// The signature format is (with sortedHashes accross all namespaces) ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] ) const swarmAsArray = Object.entries(swarm) as Array<Array<any>>;
const dataToVerify = `${usPk}${builtRequest.params.timestamp}${sortedHashes.join('')}`; if (!swarmAsArray.length) {
throw new Error(
const dataToVerifyUtf8 = StringUtils.encode(dataToVerify, 'utf8'); `Invalid JSON swarmAsArray response got for ${request.method} on snode ${ed25519Str(
const isValid = sodium.crypto_sign_verify_detached( snodeToMakeRequestTo.pubkey_ed25519
fromBase64ToArray(signatureSnode), )}, ${firstResultParsedBody}`
new Uint8Array(dataToVerifyUtf8), );
fromHexToArray(snodePubkey) }
// results will only contains the snode pubkeys which returned invalid/empty results
const results: Array<string> = compact(
swarmAsArray.map(snode => {
const snodePubkey = snode[0];
const snodeJson = snode[1];
const isFailed = snodeJson.failed || false;
if (isFailed) {
const reason = snodeJson.reason;
const statusCode = snodeJson.code;
if (reason && statusCode) {
window?.log?.warn(
`Could not ${request.method} from ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)} due to error: ${reason}: ${statusCode}`
); );
if (!isValid) { // if we tried to make the delete on a snode not in our swarm, just trigger a pRetry error so the outer block here finds new snodes to make the request to.
return snodePubkey; if (statusCode === 421) {
throw new pRetry.AbortError(
`421 error on network ${request.method}. Retrying with a new snode`
);
} }
return null; } else {
}) window?.log?.warn(
); `Could not ${request.method} from ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}`
);
}
return snodePubkey;
}
return results; const deletedObj = snodeJson.deleted as Record<number, Array<string>>;
} catch (e) { const hashes: Array<string> = [];
throw new Error(
`Invalid JSON response got for ${request.method} on snode ${ed25519Str( for (const key in deletedObj) {
snodeToMakeRequestTo.pubkey_ed25519 if (deletedObj.hasOwnProperty(key)) {
)}, ${ret}` hashes.push(...deletedObj[key]);
); }
} }
}, const sortedHashes = hashes.sort();
{ const signatureSnode = snodeJson.signature as string;
retries: 3, // The signature format is (with sortedHashes accross all namespaces) ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] )
minTimeout: SnodeAPI.TEST_getMinTimeout(), const dataToVerify = `${usPk}${builtRequest.params.timestamp}${sortedHashes.join('')}`;
onFailedAttempt: e => {
window?.log?.warn( const dataToVerifyUtf8 = StringUtils.encode(dataToVerify, 'utf8');
`${request.method} INNER request attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left...` const isValid = sodium.crypto_sign_verify_detached(
fromBase64ToArray(signatureSnode),
new Uint8Array(dataToVerifyUtf8),
fromHexToArray(snodePubkey)
); );
}, if (!isValid) {
} return snodePubkey;
); }
return null;
})
);
return results;
} catch (e) {
throw new Error(
`Invalid JSON response got for ${request.method} on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}, ${ret}`
);
}
}, },
{ {
retries: 3, retries: 5,
minTimeout: SnodeAPI.TEST_getMinTimeout(), minTimeout: SnodeAPI.TEST_getMinTimeout(),
onFailedAttempt: e => { onFailedAttempt: e => {
window?.log?.warn( window?.log?.warn(
@ -167,141 +154,120 @@ const forceNetworkDeletion = async (): Promise<Array<string> | null> => {
const TEST_getMinTimeout = () => 500; const TEST_getMinTimeout = () => 500;
/** /**
* Locally deletes message and deletes message on the network (all nodes that contain the message) * Delete the specified message hashes from the our own swarm only.
* Note: legacy group did not support removing messages from the swarm.
*/ */
const networkDeleteMessages = async ( const networkDeleteMessageOurSwarm = async (
messagesHashes: Array<string>, messagesHashes: Array<string>,
pubkey: PubkeyType | GroupPubkeyType pubkey: PubkeyType
): Promise<boolean> => { ): Promise<boolean> => {
const sodium = await getSodiumRenderer(); const sodium = await getSodiumRenderer();
if (PubKey.is05Pubkey(pubkey) && pubkey !== UserUtils.getOurPubKeyStrFromCache()) { if (!PubKey.is05Pubkey(pubkey) || pubkey !== UserUtils.getOurPubKeyStrFromCache()) {
throw new Error('networkDeleteMessages with 05 pk can only delete for ourself'); throw new Error('networkDeleteMessageOurSwarm with 05 pk can only for our own swarm');
} }
const request = PubKey.is03Pubkey(pubkey) const request = new DeleteHashesFromUserNodeSubRequest({ messagesHashes });
? new DeleteHashesFromGroupNodeSubRequest({ messagesHashes, groupPk: pubkey })
: new DeleteHashesFromUserNodeSubRequest({ messagesHashes });
try { try {
const success = await pRetry( const success = await pRetry(
async () => { async () => {
const snodeToMakeRequestTo = await SnodePool.getNodeFromSwarmOrThrow(request.pubkey); const snodeToMakeRequestTo = await SnodePool.getNodeFromSwarmOrThrow(request.pubkey);
return pRetry( const ret = await BatchRequests.doUnsignedSnodeBatchRequestNoRetries(
async () => { [request],
const ret = await BatchRequests.doUnsignedSnodeBatchRequestNoRetries( snodeToMakeRequestTo,
[request], 10000,
snodeToMakeRequestTo, request.pubkey,
10000, false
request.pubkey, );
false
if (!ret || !ret?.[0].body || ret[0].code !== 200) {
throw new Error(
`networkDeleteMessageOurSwarm: Empty response got for ${request.method} on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)} about pk: ${ed25519Str(request.pubkey)}`
);
}
try {
const firstResultParsedBody = ret[0].body;
const { swarm } = firstResultParsedBody;
if (!swarm) {
throw new Error(
`networkDeleteMessageOurSwarm: Invalid JSON swarm response got for ${request.method} on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}, ${firstResultParsedBody}`
); );
debugger; }
const swarmAsArray = Object.entries(swarm) as Array<Array<any>>;
if (!swarmAsArray.length) {
throw new Error(
`networkDeleteMessageOurSwarm: Invalid JSON swarmAsArray response got for ${request.method} on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}, ${firstResultParsedBody}`
);
}
// results will only contains the snode pubkeys which returned invalid/empty results
const results: Array<string> = compact(
swarmAsArray.map(snode => {
const snodePubkey = snode[0];
const snodeJson = snode[1];
if (!ret || !ret?.[0].body || ret[0].code !== 200) { const isFailed = snodeJson.failed || false;
throw new Error(
`Empty response got for ${request.method} on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)} about pk: ${ed25519Str(request.pubkey)}`
);
}
try {
const firstResultParsedBody = ret[0].body;
const { swarm } = firstResultParsedBody;
if (!swarm) {
throw new Error(
`Invalid JSON swarm response got for ${request.method} on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}, ${firstResultParsedBody}`
);
}
const swarmAsArray = Object.entries(swarm) as Array<Array<any>>;
if (!swarmAsArray.length) {
throw new Error(
`Invalid JSON swarmAsArray response got for ${request.method} on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}, ${firstResultParsedBody}`
);
}
// results will only contains the snode pubkeys which returned invalid/empty results
const results: Array<string> = compact(
swarmAsArray.map(snode => {
const snodePubkey = snode[0];
const snodeJson = snode[1];
const isFailed = snodeJson.failed || false;
if (isFailed) {
const reason = snodeJson.reason;
const statusCode = snodeJson.code;
if (reason && statusCode) {
window?.log?.warn(
`Could not ${request.method} from ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)} due to error: ${reason}: ${statusCode}`
);
// if we tried to make the delete on a snode not in our swarm, just trigger a pRetry error so the outer block here finds new snodes to make the request to.
if (statusCode === 421) {
throw new pRetry.AbortError(
`421 error on network ${request.method}. Retrying with a new snode`
);
}
} else {
window?.log?.warn(
`Could not ${request.method} from ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}`
);
}
return snodePubkey;
}
const responseHashes = snodeJson.deleted as Array<string>; if (isFailed) {
const signatureSnode = snodeJson.signature as string; const reason = snodeJson.reason;
// The signature looks like ( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] ) const statusCode = snodeJson.code;
const dataToVerify = `${request.pubkey}${messagesHashes.join( if (reason && statusCode) {
'' window?.log?.warn(
)}${responseHashes.join('')}`; `networkDeleteMessageOurSwarm: Could not ${request.method} from ${ed25519Str(
const dataToVerifyUtf8 = StringUtils.encode(dataToVerify, 'utf8'); snodeToMakeRequestTo.pubkey_ed25519
const isValid = sodium.crypto_sign_verify_detached( )} due to error: ${reason}: ${statusCode}`
fromBase64ToArray(signatureSnode),
new Uint8Array(dataToVerifyUtf8),
fromHexToArray(snodePubkey)
); );
if (!isValid) { } else {
return snodePubkey; window?.log?.warn(
} `networkDeleteMessageOurSwarm: Could not ${request.method} from ${ed25519Str(
return null; snodeToMakeRequestTo.pubkey_ed25519
}) )}`
); );
}
return snodePubkey;
}
return isEmpty(results); const responseHashes = snodeJson.deleted as Array<string>;
} catch (e) { const signatureSnode = snodeJson.signature as string;
throw new Error( // The signature looks like ( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] )
`Invalid JSON response got for ${request.method} on snode ${ed25519Str( const dataToVerify = `${request.pubkey}${messagesHashes.join(
snodeToMakeRequestTo.pubkey_ed25519 ''
)}, ${ret}` )}${responseHashes.join('')}`;
); const dataToVerifyUtf8 = StringUtils.encode(dataToVerify, 'utf8');
} const isValid = sodium.crypto_sign_verify_detached(
}, fromBase64ToArray(signatureSnode),
{ new Uint8Array(dataToVerifyUtf8),
retries: 3, fromHexToArray(snodePubkey)
minTimeout: SnodeAPI.TEST_getMinTimeout(),
onFailedAttempt: e => {
window?.log?.warn(
`${request.method} INNER request attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left...`
); );
}, if (!isValid) {
} return snodePubkey;
); }
return null;
})
);
return isEmpty(results);
} catch (e) {
throw new Error(
`networkDeleteMessageOurSwarm: Invalid JSON response got for ${request.method} on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)}, ${ret}`
);
}
}, },
{ {
retries: 3, retries: 5,
minTimeout: SnodeAPI.TEST_getMinTimeout(), minTimeout: SnodeAPI.TEST_getMinTimeout(),
onFailedAttempt: e => { onFailedAttempt: e => {
window?.log?.warn( window?.log?.warn(
`${request.method} OUTER request attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left... ${e.message}` `networkDeleteMessageOurSwarm: ${request.method} request attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left... ${e.message}`
); );
}, },
} }
@ -309,13 +275,85 @@ const networkDeleteMessages = async (
return success; return success;
} catch (e) { } catch (e) {
window?.log?.warn(`failed to ${request.method} message on network:`, e); window?.log?.warn(
`networkDeleteMessageOurSwarm: failed to ${request.method} message on network:`,
e
);
return false;
}
};
/**
* Delete the specified message hashes from the 03-group's swarm.
* Returns true when the hashes have been removed successufuly.
* Returns false when
* - we don't have the secretKey
* - if one of the hash was already not present in the swarm,
* - if the request failed too many times
*/
const networkDeleteMessagesForGroup = async (
messagesHashes: Array<string>,
groupPk: GroupPubkeyType
): Promise<boolean> => {
if (!PubKey.is03Pubkey(groupPk)) {
throw new Error('networkDeleteMessagesForGroup with 05 pk can only delete for ourself');
}
const group = await UserGroupsWrapperActions.getGroup(groupPk);
if (!group || !group.secretKey || isEmpty(group.secretKey)) {
window.log.warn(
`networkDeleteMessagesForGroup: not deleting from swarm of 03-group ${messagesHashes.length} hashes as we do not the adminKey`
);
return false;
}
try {
const request = new DeleteHashesFromGroupNodeSubRequest({
messagesHashes,
groupPk,
secretKey: group.secretKey,
});
await pRetry(
async () => {
const snodeToMakeRequestTo = await SnodePool.getNodeFromSwarmOrThrow(request.pubkey);
const ret = await BatchRequests.doUnsignedSnodeBatchRequestNoRetries(
[request],
snodeToMakeRequestTo,
10000,
request.pubkey,
false
);
if (!ret || !ret?.[0].body || ret[0].code !== 200) {
throw new Error(
`networkDeleteMessagesForGroup: Empty response got for ${request.method} on snode ${ed25519Str(
snodeToMakeRequestTo.pubkey_ed25519
)} about pk: ${ed25519Str(request.pubkey)}`
);
}
},
{
retries: 5,
minTimeout: SnodeAPI.TEST_getMinTimeout(),
onFailedAttempt: e => {
window?.log?.warn(
`networkDeleteMessagesForGroup: ${request.method} request attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left... ${e.message}`
);
},
}
);
return true;
} catch (e) {
window?.log?.warn(`networkDeleteMessagesForGroup: failed to delete messages on network:`, e);
return false; return false;
} }
}; };
export const SnodeAPI = { export const SnodeAPI = {
TEST_getMinTimeout, TEST_getMinTimeout,
networkDeleteMessages, networkDeleteMessagesForGroup,
networkDeleteMessageOurSwarm,
forceNetworkDeletion, forceNetworkDeletion,
}; };

@ -3,10 +3,10 @@ import { GroupPubkeyType, PubkeyType, WithGroupPubkey } from 'libsession_util_no
import { from_hex } from 'libsodium-wrappers-sumo'; import { from_hex } from 'libsodium-wrappers-sumo';
import { isEmpty, isString } from 'lodash'; import { isEmpty, isString } from 'lodash';
import { AwaitedReturn, assertUnreachable } from '../../../types/sqlSharedTypes'; import { AwaitedReturn, assertUnreachable } from '../../../types/sqlSharedTypes';
import { UserGroupsWrapperActions } from '../../../webworker/workers/browser/libsession_worker_interface';
import { concatUInt8Array } from '../../crypto'; import { concatUInt8Array } from '../../crypto';
import { PubKey } from '../../types'; import { PubKey } from '../../types';
import { StringUtils, UserUtils } from '../../utils'; import { StringUtils, UserUtils } from '../../utils';
import { ed25519Str } from '../../utils/String';
import { GetNetworkTime } from './getNetworkTime'; import { GetNetworkTime } from './getNetworkTime';
import { import {
SnodeNamespace, SnodeNamespace,
@ -25,7 +25,6 @@ import {
WithSignature, WithSignature,
WithTimestamp, WithTimestamp,
} from './types'; } from './types';
import { ed25519Str } from '../../utils/String';
type WithMaxSize = { max_size?: number }; type WithMaxSize = { max_size?: number };
export type WithShortenOrExtend = { shortenOrExtend: 'shorten' | 'extend' | '' }; export type WithShortenOrExtend = { shortenOrExtend: 'shorten' | 'extend' | '' };
@ -559,25 +558,25 @@ export class DeleteHashesFromGroupNodeSubRequest extends SnodeAPISubRequest {
public method = 'delete' as const; public method = 'delete' as const;
public readonly messageHashes: Array<string>; public readonly messageHashes: Array<string>;
public readonly pubkey: GroupPubkeyType; public readonly pubkey: GroupPubkeyType;
public readonly secretKey: Uint8Array;
constructor(args: WithMessagesHashes & WithGroupPubkey) { constructor(args: WithMessagesHashes & WithGroupPubkey & WithSecretKey) {
super(); super();
this.messageHashes = args.messagesHashes; this.messageHashes = args.messagesHashes;
this.pubkey = args.groupPk; this.pubkey = args.groupPk;
this.secretKey = args.secretKey;
if (!this.secretKey || isEmpty(this.secretKey)) {
throw new Error('DeleteHashesFromGroupNodeSubRequest needs a secretKey');
}
} }
public async buildAndSignParameters() { public async buildAndSignParameters() {
const group = await UserGroupsWrapperActions.getGroup(this.pubkey); // Note: this request can only be made by an admin and will be denied otherwise, so we make the secretKey mandatory in the constructor.
if (!group) {
throw new Error('DeleteHashesFromGroupNodeSubRequest no such group found');
}
// This will try to use the adminSecretKey if we have it, or the authData if we have it.
// Otherwise, it will throw
const signResult = await SnodeGroupSignature.getGroupSignatureByHashesParams({ const signResult = await SnodeGroupSignature.getGroupSignatureByHashesParams({
method: this.method, method: this.method,
messagesHashes: this.messageHashes, messagesHashes: this.messageHashes,
groupPk: this.pubkey, groupPk: this.pubkey,
group, group: { authData: null, pubkeyHex: this.pubkey, secretKey: this.secretKey },
}); });
return { return {
@ -714,8 +713,9 @@ export class StoreGroupConfigOrMessageSubRequest extends SnodeAPISubRequest {
public readonly destination: GroupPubkeyType; public readonly destination: GroupPubkeyType;
public readonly ttlMs: number; public readonly ttlMs: number;
public readonly encryptedData: Uint8Array; public readonly encryptedData: Uint8Array;
public readonly dbMessageIdentifier: string | null; public readonly dbMessageIdentifier: string | null;
public readonly secretKey: Uint8Array | null;
public readonly authData: Uint8Array | null;
constructor( constructor(
args: WithGroupPubkey & { args: WithGroupPubkey & {
@ -726,6 +726,8 @@ export class StoreGroupConfigOrMessageSubRequest extends SnodeAPISubRequest {
ttlMs: number; ttlMs: number;
encryptedData: Uint8Array; encryptedData: Uint8Array;
dbMessageIdentifier: string | null; dbMessageIdentifier: string | null;
authData: Uint8Array | null;
secretKey: Uint8Array | null;
} }
) { ) {
super(); super();
@ -734,6 +736,8 @@ export class StoreGroupConfigOrMessageSubRequest extends SnodeAPISubRequest {
this.ttlMs = args.ttlMs; this.ttlMs = args.ttlMs;
this.encryptedData = args.encryptedData; this.encryptedData = args.encryptedData;
this.dbMessageIdentifier = args.dbMessageIdentifier; this.dbMessageIdentifier = args.dbMessageIdentifier;
this.authData = args.authData;
this.secretKey = args.secretKey;
if (isEmpty(this.encryptedData)) { if (isEmpty(this.encryptedData)) {
throw new Error('this.encryptedData cannot be empty'); throw new Error('this.encryptedData cannot be empty');
@ -743,6 +747,16 @@ export class StoreGroupConfigOrMessageSubRequest extends SnodeAPISubRequest {
'StoreGroupConfigOrMessageSubRequest: groupconfig namespace required a 03 pubkey' 'StoreGroupConfigOrMessageSubRequest: groupconfig namespace required a 03 pubkey'
); );
} }
if (isEmpty(this.secretKey) && isEmpty(this.authData)) {
throw new Error(
'StoreGroupConfigOrMessageSubRequest needs either authData or secretKey to be set'
);
}
if (SnodeNamespace.isGroupConfigNamespace(this.namespace) && isEmpty(this.secretKey)) {
throw new Error(
`StoreGroupConfigOrMessageSubRequest: groupconfig namespace [${this.namespace}] requires an adminSecretKey`
);
}
} }
public async buildAndSignParameters(): Promise<{ public async buildAndSignParameters(): Promise<{
@ -751,17 +765,11 @@ export class StoreGroupConfigOrMessageSubRequest extends SnodeAPISubRequest {
}> { }> {
const encryptedDataBase64 = ByteBuffer.wrap(this.encryptedData).toString('base64'); const encryptedDataBase64 = ByteBuffer.wrap(this.encryptedData).toString('base64');
const found = await UserGroupsWrapperActions.getGroup(this.destination);
if (SnodeNamespace.isGroupConfigNamespace(this.namespace) && isEmpty(found?.secretKey)) {
throw new Error(
`groupconfig namespace [${this.namespace}] require an adminSecretKey for signature but we found none`
);
}
// this will either sign with our admin key or with the subaccount key if the admin one isn't there // this will either sign with our admin key or with the subaccount key if the admin one isn't there
const signDetails = await SnodeGroupSignature.getSnodeGroupSignature({ const signDetails = await SnodeGroupSignature.getSnodeGroupSignature({
method: this.method, method: this.method,
namespace: this.namespace, namespace: this.namespace,
group: found, group: { authData: this.authData, pubkeyHex: this.destination, secretKey: this.secretKey },
}); });
if (!signDetails) { if (!signDetails) {

@ -293,7 +293,6 @@ async function getGroupSignatureByHashesParams({
const signatureTimestamp = GetNetworkTime.now(); const signatureTimestamp = GetNetworkTime.now();
const sodium = await getSodiumRenderer(); const sodium = await getSodiumRenderer();
// N
try { try {
if (group.secretKey && !isEmpty(group.secretKey)) { if (group.secretKey && !isEmpty(group.secretKey)) {
const signature = sodium.crypto_sign_detached(message, group.secretKey); const signature = sodium.crypto_sign_detached(message, group.secretKey);
@ -303,7 +302,7 @@ async function getGroupSignatureByHashesParams({
signature: signatureBase64, signature: signatureBase64,
pubkey: group.pubkeyHex, pubkey: group.pubkeyHex,
messages: messagesHashes, messages: messagesHashes,
timestamp: signatureTimestamp, // TODO audric is this causing backend signature issues? timestamp: signatureTimestamp,
}; };
} }

@ -50,7 +50,7 @@ import {
} from '../apis/snode_api/signature/groupSignature'; } from '../apis/snode_api/signature/groupSignature';
import { SnodeSignature, SnodeSignatureResult } from '../apis/snode_api/signature/snodeSignatures'; import { SnodeSignature, SnodeSignatureResult } from '../apis/snode_api/signature/snodeSignatures';
import { SnodePool } from '../apis/snode_api/snodePool'; import { SnodePool } from '../apis/snode_api/snodePool';
import { WithMessagesHashes, WithRevokeSubRequest } from '../apis/snode_api/types'; import { WithRevokeSubRequest } from '../apis/snode_api/types';
import { TTL_DEFAULT } from '../constants'; import { TTL_DEFAULT } from '../constants';
import { ConvoHub } from '../conversations'; import { ConvoHub } from '../conversations';
import { MessageEncrypter } from '../crypto/MessageEncrypter'; import { MessageEncrypter } from '../crypto/MessageEncrypter';
@ -184,6 +184,13 @@ async function sendSingleMessage({
); );
} }
} else if (PubKey.is03Pubkey(destination)) { } else if (PubKey.is03Pubkey(destination)) {
const group = await UserGroupsWrapperActions.getGroup(destination);
if (!group) {
window.log.warn(
`sendSingleMessage: no such group found in wrapper: ${ed25519Str(destination)}`
);
throw new Error('sendSingleMessage: no such group found in wrapper');
}
if (SnodeNamespace.isGroupConfigNamespace(encryptedAndWrapped.namespace)) { if (SnodeNamespace.isGroupConfigNamespace(encryptedAndWrapped.namespace)) {
subRequests.push( subRequests.push(
new StoreGroupConfigOrMessageSubRequest({ new StoreGroupConfigOrMessageSubRequest({
@ -192,6 +199,7 @@ async function sendSingleMessage({
ttlMs: overridenTtl, ttlMs: overridenTtl,
groupPk: destination, groupPk: destination,
dbMessageIdentifier: encryptedAndWrapped.identifier || null, dbMessageIdentifier: encryptedAndWrapped.identifier || null,
...group,
}) })
); );
} else if (encryptedAndWrapped.namespace === SnodeNamespaces.ClosedGroupMessages) { } else if (encryptedAndWrapped.namespace === SnodeNamespaces.ClosedGroupMessages) {
@ -202,6 +210,7 @@ async function sendSingleMessage({
ttlMs: overridenTtl, ttlMs: overridenTtl,
groupPk: destination, groupPk: destination,
dbMessageIdentifier: encryptedAndWrapped.identifier || null, dbMessageIdentifier: encryptedAndWrapped.identifier || null,
...group,
}) })
); );
} else { } else {
@ -338,38 +347,33 @@ async function signSubRequests(
return signedRequests; return signedRequests;
} }
async function sendMessagesDataToSnode( async function sendMessagesDataToSnode<T extends PubkeyType | GroupPubkeyType>(
storeRequests: Array< storeRequests: Array<
| StoreGroupConfigOrMessageSubRequest | StoreGroupConfigOrMessageSubRequest
| StoreUserConfigSubRequest | StoreUserConfigSubRequest
| StoreUserMessageSubRequest | StoreUserMessageSubRequest
| StoreLegacyGroupMessageSubRequest | StoreLegacyGroupMessageSubRequest
>, >,
asssociatedWith: PubkeyType | GroupPubkeyType, asssociatedWith: T,
{ {
messagesHashes: messagesToDelete,
revokeSubRequest, revokeSubRequest,
unrevokeSubRequest, unrevokeSubRequest,
deleteHashesSubRequest,
deleteAllMessagesSubRequest, deleteAllMessagesSubRequest,
}: WithMessagesHashes & }: WithRevokeSubRequest & {
WithRevokeSubRequest & { deleteAllMessagesSubRequest?: DeleteAllFromGroupMsgNodeSubRequest | null;
deleteAllMessagesSubRequest?: DeleteAllFromGroupMsgNodeSubRequest | null; deleteHashesSubRequest:
}, | (T extends PubkeyType
? DeleteHashesFromUserNodeSubRequest
: DeleteHashesFromGroupNodeSubRequest)
| null;
},
method: MethodBatchType method: MethodBatchType
): Promise<NotEmptyArrayOfBatchResults> { ): Promise<NotEmptyArrayOfBatchResults> {
if (!asssociatedWith) { if (!asssociatedWith) {
throw new Error('sendMessagesDataToSnode first subrequest pubkey needs to be set'); throw new Error('sendMessagesDataToSnode first subrequest pubkey needs to be set');
} }
const deleteHashesSubRequest = !messagesToDelete.length
? null
: PubKey.is05Pubkey(asssociatedWith)
? new DeleteHashesFromUserNodeSubRequest({ messagesHashes: messagesToDelete })
: new DeleteHashesFromGroupNodeSubRequest({
messagesHashes: messagesToDelete,
groupPk: asssociatedWith,
});
if (storeRequests.some(m => m.destination !== asssociatedWith)) { if (storeRequests.some(m => m.destination !== asssociatedWith)) {
throw new Error( throw new Error(
'sendMessagesDataToSnode tried to send batchrequest containing subrequest not for the right destination' 'sendMessagesDataToSnode tried to send batchrequest containing subrequest not for the right destination'
@ -565,17 +569,21 @@ async function encryptMessagesAndWrap(
* @param destination the pubkey we should deposit those message to * @param destination the pubkey we should deposit those message to
* @returns the hashes of successful deposit * @returns the hashes of successful deposit
*/ */
async function sendEncryptedDataToSnode({ async function sendEncryptedDataToSnode<T extends GroupPubkeyType | PubkeyType>({
destination, destination,
storeRequests, storeRequests,
messagesHashesToDelete, deleteHashesSubRequest,
revokeSubRequest, revokeSubRequest,
unrevokeSubRequest, unrevokeSubRequest,
deleteAllMessagesSubRequest, deleteAllMessagesSubRequest,
}: WithRevokeSubRequest & { }: WithRevokeSubRequest & {
storeRequests: Array<StoreGroupConfigOrMessageSubRequest | StoreUserConfigSubRequest>; storeRequests: Array<StoreGroupConfigOrMessageSubRequest | StoreUserConfigSubRequest>;
destination: GroupPubkeyType | PubkeyType; destination: T;
messagesHashesToDelete: Set<string> | null; deleteHashesSubRequest:
| (T extends PubkeyType
? DeleteHashesFromUserNodeSubRequest
: DeleteHashesFromGroupNodeSubRequest)
| null;
deleteAllMessagesSubRequest?: DeleteAllFromGroupMsgNodeSubRequest | null; deleteAllMessagesSubRequest?: DeleteAllFromGroupMsgNodeSubRequest | null;
}): Promise<NotEmptyArrayOfBatchResults | null> { }): Promise<NotEmptyArrayOfBatchResults | null> {
try { try {
@ -585,7 +593,7 @@ async function sendEncryptedDataToSnode({
storeRequests, storeRequests,
destination, destination,
{ {
messagesHashes: [...(messagesHashesToDelete || [])], deleteHashesSubRequest,
revokeSubRequest, revokeSubRequest,
unrevokeSubRequest, unrevokeSubRequest,
deleteAllMessagesSubRequest, deleteAllMessagesSubRequest,

@ -139,7 +139,6 @@ class GroupPendingRemovalsJob extends PersistedJob<GroupPendingRemovalsPersisted
return concatUInt8Array(s, StringUtils.stringToUint8Array(`${currentGen}`)); return concatUInt8Array(s, StringUtils.stringToUint8Array(`${currentGen}`));
}); });
debugger;
const multiEncryptedMessage = await MultiEncryptWrapperActions.multiEncrypt({ const multiEncryptedMessage = await MultiEncryptWrapperActions.multiEncrypt({
messages: dataToEncrypt, messages: dataToEncrypt,
recipients: sessionIds, recipients: sessionIds,
@ -161,15 +160,17 @@ class GroupPendingRemovalsJob extends PersistedJob<GroupPendingRemovalsPersisted
dbMessageIdentifier: null, dbMessageIdentifier: null,
namespace: SnodeNamespaces.ClosedGroupRevokedRetrievableMessages, namespace: SnodeNamespaces.ClosedGroupRevokedRetrievableMessages,
ttlMs: TTL_DEFAULT.CONTENT_MESSAGE, ttlMs: TTL_DEFAULT.CONTENT_MESSAGE,
secretKey: group.secretKey,
authData: null,
}); });
const result = await MessageSender.sendEncryptedDataToSnode({ const result = await MessageSender.sendEncryptedDataToSnode({
storeRequests: [multiEncryptRequest], storeRequests: [multiEncryptRequest],
destination: groupPk, destination: groupPk,
messagesHashesToDelete: null, deleteHashesSubRequest: null,
...revokeUnrevokeParams, ...revokeUnrevokeParams,
}); });
console.warn('result', result);
if (result?.length === 2 && result[0].code === 200 && result[1].code === 200) { if (result?.length === 2 && result[0].code === 200 && result[1].code === 200) {
// both requests success, remove the members from the group member entirely and sync // both requests success, remove the members from the group member entirely and sync
await MetaGroupWrapperActions.memberEraseAndRekey(groupPk, sessionIdsHex); await MetaGroupWrapperActions.memberEraseAndRekey(groupPk, sessionIdsHex);

@ -5,9 +5,13 @@ import { UserUtils } from '../..';
import { SignalService } from '../../../../protobuf'; import { SignalService } from '../../../../protobuf';
import { assertUnreachable } from '../../../../types/sqlSharedTypes'; import { assertUnreachable } from '../../../../types/sqlSharedTypes';
import { isSignInByLinking } from '../../../../util/storage'; import { isSignInByLinking } from '../../../../util/storage';
import { MetaGroupWrapperActions } from '../../../../webworker/workers/browser/libsession_worker_interface'; import {
MetaGroupWrapperActions,
UserGroupsWrapperActions,
} from '../../../../webworker/workers/browser/libsession_worker_interface';
import { import {
DeleteAllFromGroupMsgNodeSubRequest, DeleteAllFromGroupMsgNodeSubRequest,
DeleteHashesFromGroupNodeSubRequest,
StoreGroupConfigOrMessageSubRequest, StoreGroupConfigOrMessageSubRequest,
StoreGroupExtraData, StoreGroupExtraData,
} from '../../../apis/snode_api/SnodeRequestTypes'; } from '../../../apis/snode_api/SnodeRequestTypes';
@ -85,6 +89,14 @@ async function storeGroupUpdateMessages({
return true; return true;
} }
const group = await UserGroupsWrapperActions.getGroup(groupPk);
if (!group) {
window.log.warn(
`storeGroupUpdateMessages for ${ed25519Str(groupPk)}: no group found in wrapper`
);
return false;
}
const updateMessagesToEncrypt: Array<StoreGroupExtraData> = updateMessages.map(updateMessage => { const updateMessagesToEncrypt: Array<StoreGroupExtraData> = updateMessages.map(updateMessage => {
const wrapped = MessageSender.wrapContentIntoEnvelope( const wrapped = MessageSender.wrapContentIntoEnvelope(
SignalService.Envelope.Type.SESSION_MESSAGE, SignalService.Envelope.Type.SESSION_MESSAGE,
@ -122,13 +134,14 @@ async function storeGroupUpdateMessages({
namespace: m.namespace, namespace: m.namespace,
ttlMs: m.ttl, ttlMs: m.ttl,
dbMessageIdentifier: m.dbMessageIdentifier, dbMessageIdentifier: m.dbMessageIdentifier,
...group,
}); });
}); });
const result = await MessageSender.sendEncryptedDataToSnode({ const result = await MessageSender.sendEncryptedDataToSnode({
storeRequests: [...updateMessagesRequests], storeRequests: [...updateMessagesRequests],
destination: groupPk, destination: groupPk,
messagesHashesToDelete: null, deleteHashesSubRequest: null,
revokeSubRequest: null, revokeSubRequest: null,
unrevokeSubRequest: null, unrevokeSubRequest: null,
deleteAllMessagesSubRequest: null, deleteAllMessagesSubRequest: null,
@ -172,6 +185,13 @@ async function pushChangesToGroupSwarmIfNeeded({
!unrevokeSubRequest && !unrevokeSubRequest &&
!deleteAllMessagesSubRequest !deleteAllMessagesSubRequest
) { ) {
window.log.debug(`pushChangesToGroupSwarmIfNeeded: ${ed25519Str(groupPk)}: nothing to push`);
return RunJobResult.Success;
}
const group = await UserGroupsWrapperActions.getGroup(groupPk);
if (!group) {
window.log.debug(`pushChangesToGroupSwarmIfNeeded: ${ed25519Str(groupPk)}: group not found`);
return RunJobResult.Success; return RunJobResult.Success;
} }
@ -208,25 +228,75 @@ async function pushChangesToGroupSwarmIfNeeded({
data: keysEncrypted[index], data: keysEncrypted[index],
})); }));
const pendingConfigRequests = pendingConfigMsgs.map(m => { let pendingConfigRequests: Array<StoreGroupConfigOrMessageSubRequest> = [];
return new StoreGroupConfigOrMessageSubRequest({ let keysEncryptedRequests: Array<StoreGroupConfigOrMessageSubRequest> = [];
encryptedData: m.data,
groupPk, if (pendingConfigMsgs.length) {
namespace: m.namespace, if (!group.secretKey || isEmpty(group.secretKey)) {
ttlMs: m.ttl, window.log.debug(
dbMessageIdentifier: null, // those are config messages only, they have no dbMessageIdentifier `pushChangesToGroupSwarmIfNeeded: ${ed25519Str(groupPk)}: pendingConfigMsgs not empty but we do not have the secretKey`
);
throw new Error(
'pushChangesToGroupSwarmIfNeeded: pendingConfigMsgs not empty but we do not have the secretKey'
);
}
pendingConfigRequests = pendingConfigMsgs.map(m => {
return new StoreGroupConfigOrMessageSubRequest({
encryptedData: m.data,
groupPk,
namespace: m.namespace,
ttlMs: m.ttl,
dbMessageIdentifier: null, // those are config messages only, they have no dbMessageIdentifier
secretKey: group.secretKey,
authData: null,
});
}); });
}); }
const keysEncryptedRequests = keysEncryptedmessage.map(m => { if (keysEncryptedmessage.length) {
return new StoreGroupConfigOrMessageSubRequest({ if (!group.secretKey || isEmpty(group.secretKey)) {
encryptedData: m.data, window.log.debug(
`pushChangesToGroupSwarmIfNeeded: ${ed25519Str(groupPk)}: keysEncryptedmessage not empty but we do not have the secretKey`
);
throw new Error(
'pushChangesToGroupSwarmIfNeeded: keysEncryptedmessage not empty but we do not have the secretKey'
);
}
keysEncryptedRequests = keysEncryptedmessage.map(m => {
return new StoreGroupConfigOrMessageSubRequest({
encryptedData: m.data,
groupPk,
namespace: m.namespace,
ttlMs: m.ttl,
dbMessageIdentifier: null, // those are supplemental keys messages only, they have no dbMessageIdentifier
secretKey: group.secretKey,
authData: null,
});
});
}
let deleteHashesSubRequest: DeleteHashesFromGroupNodeSubRequest | null = null;
const allOldHashesArray = [...allOldHashes];
if (allOldHashesArray.length) {
if (!group.secretKey || isEmpty(group.secretKey)) {
window.log.debug(
`pushChangesToGroupSwarmIfNeeded: ${ed25519Str(groupPk)}: allOldHashesArray not empty but we do not have the secretKey`
);
throw new Error(
'pushChangesToGroupSwarmIfNeeded: allOldHashesArray not empty but we do not have the secretKey'
);
}
deleteHashesSubRequest = new DeleteHashesFromGroupNodeSubRequest({
messagesHashes: [...allOldHashes],
groupPk, groupPk,
namespace: m.namespace, secretKey: group.secretKey,
ttlMs: m.ttl,
dbMessageIdentifier: null, // those are supplemental keys messages only, they have no dbMessageIdentifier
}); });
}); }
if ( if (
revokeSubRequest?.revokeTokenHex.length === 0 || revokeSubRequest?.revokeTokenHex.length === 0 ||
@ -240,7 +310,7 @@ async function pushChangesToGroupSwarmIfNeeded({
const result = await MessageSender.sendEncryptedDataToSnode({ const result = await MessageSender.sendEncryptedDataToSnode({
storeRequests: [...pendingConfigRequests, ...keysEncryptedRequests], storeRequests: [...pendingConfigRequests, ...keysEncryptedRequests],
destination: groupPk, destination: groupPk,
messagesHashesToDelete: allOldHashes, deleteHashesSubRequest,
revokeSubRequest, revokeSubRequest,
unrevokeSubRequest, unrevokeSubRequest,
deleteAllMessagesSubRequest, deleteAllMessagesSubRequest,

@ -8,7 +8,10 @@ import { ConfigDumpData } from '../../../../data/configDump/configDump';
import { UserSyncJobDone } from '../../../../shims/events'; import { UserSyncJobDone } from '../../../../shims/events';
import { isSignInByLinking } from '../../../../util/storage'; import { isSignInByLinking } from '../../../../util/storage';
import { GenericWrapperActions } from '../../../../webworker/workers/browser/libsession_worker_interface'; import { GenericWrapperActions } from '../../../../webworker/workers/browser/libsession_worker_interface';
import { StoreUserConfigSubRequest } from '../../../apis/snode_api/SnodeRequestTypes'; import {
DeleteHashesFromUserNodeSubRequest,
StoreUserConfigSubRequest,
} from '../../../apis/snode_api/SnodeRequestTypes';
import { TTL_DEFAULT } from '../../../constants'; import { TTL_DEFAULT } from '../../../constants';
import { ConvoHub } from '../../../conversations'; import { ConvoHub } from '../../../conversations';
import { MessageSender } from '../../../sending/MessageSender'; import { MessageSender } from '../../../sending/MessageSender';
@ -109,7 +112,9 @@ async function pushChangesToUserSwarmIfNeeded() {
const result = await MessageSender.sendEncryptedDataToSnode({ const result = await MessageSender.sendEncryptedDataToSnode({
storeRequests, storeRequests,
destination: us, destination: us,
messagesHashesToDelete: changesToPush.allOldHashes, deleteHashesSubRequest: new DeleteHashesFromUserNodeSubRequest({
messagesHashes: [...changesToPush.allOldHashes],
}),
revokeSubRequest: null, revokeSubRequest: null,
unrevokeSubRequest: null, unrevokeSubRequest: null,
}); });

Loading…
Cancel
Save