fix: don't update state on updates already included in our syncmessage
This commit is contained in:
parent
2068737cdd
commit
db9fa14213
|
@ -14,6 +14,11 @@ const hasSyncedInitialConfigurationItem = 'hasSyncedInitialConfigurationItem';
|
|||
const lastAvatarUploadTimestamp = 'lastAvatarUploadTimestamp';
|
||||
const hasLinkPreviewPopupBeenDisplayed = 'hasLinkPreviewPopupBeenDisplayed';
|
||||
|
||||
// user config tracking timestamps (to discard incoming messages which would make a change we reverted in the last config message we merged)
|
||||
const latestUserProfileEnvelopeTimestamp = 'latestUserProfileEnvelopeTimestamp';
|
||||
const latestUserGroupEnvelopeTimestamp = 'latestUserGroupEnvelopeTimestamp';
|
||||
const latestUserContactsEnvelopeTimestamp = 'latestUserContactsEnvelopeTimestamp';
|
||||
|
||||
export const SettingsKey = {
|
||||
settingsReadReceipt,
|
||||
settingsTypingIndicator,
|
||||
|
@ -29,6 +34,9 @@ export const SettingsKey = {
|
|||
hasSyncedInitialConfigurationItem,
|
||||
lastAvatarUploadTimestamp,
|
||||
hasLinkPreviewPopupBeenDisplayed,
|
||||
latestUserProfileEnvelopeTimestamp,
|
||||
latestUserGroupEnvelopeTimestamp,
|
||||
latestUserContactsEnvelopeTimestamp,
|
||||
} as const;
|
||||
|
||||
export const KNOWN_BLINDED_KEYS_ITEM = 'KNOWN_BLINDED_KEYS_ITEM';
|
||||
|
|
|
@ -1,25 +1,29 @@
|
|||
import { Data } from '../../ts/data/data';
|
||||
import { SignalService } from '../protobuf';
|
||||
import { removeFromCache } from './cache';
|
||||
import { EnvelopePlus } from './types';
|
||||
import { PubKey } from '../session/types';
|
||||
import { toHex } from '../session/utils/String';
|
||||
import { getMessageQueue } from '../session';
|
||||
import { getConversationController } from '../session/conversations';
|
||||
import * as ClosedGroup from '../session/group/closed-group';
|
||||
import { PubKey } from '../session/types';
|
||||
import { toHex } from '../session/utils/String';
|
||||
import { BlockedNumberController } from '../util';
|
||||
import { getMessageQueue } from '../session';
|
||||
import { removeFromCache } from './cache';
|
||||
import { decryptWithSessionProtocol } from './contentMessage';
|
||||
import { Data } from '../../ts/data/data';
|
||||
import { EnvelopePlus } from './types';
|
||||
|
||||
import { ECKeyPair, HexKeyPair } from './keypairs';
|
||||
import { UserUtils } from '../session/utils';
|
||||
import _, { isNumber, toNumber } from 'lodash';
|
||||
import { ConversationModel } from '../models/conversation';
|
||||
import _ from 'lodash';
|
||||
import { ClosedGroupEncryptionPairReplyMessage } from '../session/messages/outgoing/controlMessage/group/ClosedGroupEncryptionPairReplyMessage';
|
||||
import { queueAllCachedFromSource } from './receiver';
|
||||
import { getSwarmPollingInstance } from '../session/apis/snode_api';
|
||||
import { perfEnd, perfStart } from '../session/utils/Performance';
|
||||
import { ConversationTypeEnum } from '../models/conversationAttributes';
|
||||
import { getSwarmPollingInstance } from '../session/apis/snode_api';
|
||||
import { SnodeNamespaces } from '../session/apis/snode_api/namespaces';
|
||||
import { ClosedGroupEncryptionPairReplyMessage } from '../session/messages/outgoing/controlMessage/group/ClosedGroupEncryptionPairReplyMessage';
|
||||
import { UserUtils } from '../session/utils';
|
||||
import { perfEnd, perfStart } from '../session/utils/Performance';
|
||||
import { ReleasedFeatures } from '../util/releaseFeature';
|
||||
import { Storage } from '../util/storage';
|
||||
import { ConfigWrapperObjectTypes } from '../webworker/workers/browser/libsession_worker_functions';
|
||||
import { getSettingsKeyFromLibsessionWrapper } from './configMessage';
|
||||
import { ECKeyPair, HexKeyPair } from './keypairs';
|
||||
import { queueAllCachedFromSource } from './receiver';
|
||||
|
||||
export const distributingClosedGroupEncryptionKeyPairs = new Map<string, ECKeyPair>();
|
||||
|
||||
|
@ -113,7 +117,7 @@ export async function handleClosedGroupControlMessage(
|
|||
);
|
||||
return;
|
||||
}
|
||||
await handleNewClosedGroup(envelope, groupUpdate);
|
||||
await handleNewClosedGroup(envelope, groupUpdate, false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -199,9 +203,48 @@ function sanityCheckNewGroup(
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we merged a more recent wrapper, we must not apply the changes from some incoming messages as it would override a change already set in the wrapper.
|
||||
*
|
||||
* This is mostly to take care of the link a device logic, where we apply the changes from a wrapper, and then start polling from our swarm namespace 0.
|
||||
* Some messages on our swarm might unhide a contact which was marked hidden after that message was already received on another device. Same for groups left/joined etc.
|
||||
*
|
||||
* @returns true if the user config release is live AND the latest processed corresponding wrapper is supposed to have already included the changes this message did.
|
||||
* So if that message should not make any changes to the ata tracked in the wrappers (just add messages if needed, but don't set members, unhide contact etc).
|
||||
*/
|
||||
export async function sentAtMoreRecentThanWrapper(
|
||||
envelopeSentAtMs: number,
|
||||
variant: ConfigWrapperObjectTypes
|
||||
): Promise<'unknown' | 'wrapper_more_recent' | 'envelope_more_recent'> {
|
||||
const userConfigReleased = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
|
||||
if (!userConfigReleased) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
const settingsKey = getSettingsKeyFromLibsessionWrapper(variant);
|
||||
if (!settingsKey) {
|
||||
return 'unknown';
|
||||
}
|
||||
const latestProcessedEnvelope = Storage.get(settingsKey);
|
||||
if (!isNumber(latestProcessedEnvelope) || !latestProcessedEnvelope) {
|
||||
// We want to process the message if we do not have valid data in the db.
|
||||
// Also, we DO want to process a message if we DO NOT have a latest processed timestamp for that wrapper yet
|
||||
return 'envelope_more_recent';
|
||||
}
|
||||
|
||||
// this must return true if the message we are considering should have already been handled based on our `latestProcessedEnvelope`.
|
||||
// so if that message was sent before `latestProcessedEnvelope - 2 mins`, we must return true;
|
||||
const latestProcessedEnvelopeLess2Mins = latestProcessedEnvelope - 2 * 60 * 1000;
|
||||
|
||||
return envelopeSentAtMs > latestProcessedEnvelopeLess2Mins
|
||||
? 'envelope_more_recent'
|
||||
: 'wrapper_more_recent';
|
||||
}
|
||||
|
||||
export async function handleNewClosedGroup(
|
||||
envelope: EnvelopePlus,
|
||||
groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage
|
||||
groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage,
|
||||
fromLegacyConfig: boolean
|
||||
) {
|
||||
if (groupUpdate.type !== SignalService.DataMessage.ClosedGroupControlMessage.Type.NEW) {
|
||||
return;
|
||||
|
@ -229,10 +272,21 @@ export async function handleNewClosedGroup(
|
|||
const groupId = toHex(publicKey);
|
||||
const members = membersAsData.map(toHex);
|
||||
const admins = adminsAsData.map(toHex);
|
||||
const envelopeTimestamp = _.toNumber(envelope.timestamp);
|
||||
const envelopeTimestamp = toNumber(envelope.timestamp);
|
||||
// a type new is sent and received on one to one so do not use envelope.senderIdentity here
|
||||
const sender = envelope.source;
|
||||
|
||||
if (
|
||||
!fromLegacyConfig &&
|
||||
(await sentAtMoreRecentThanWrapper(envelopeTimestamp, 'UserGroupsConfig')) ===
|
||||
'wrapper_more_recent'
|
||||
) {
|
||||
// not from legacy config, so this is a new closed group deposited on our swarm by a user.
|
||||
// we do not want to process it if our wrapper is more recent that that invite to group envelope.
|
||||
window.log.info('dropping invite to legacy group because our wrapper is more recent');
|
||||
return removeFromCache(envelope);
|
||||
}
|
||||
|
||||
if (!members.includes(ourNumber.key)) {
|
||||
window?.log?.info(
|
||||
'Got a new group message but apparently we are not a member of it. Dropping it.'
|
||||
|
@ -276,7 +330,7 @@ export async function handleNewClosedGroup(
|
|||
groupConvo.set({
|
||||
left: false,
|
||||
isKickedFromGroup: false,
|
||||
lastJoinedTimestamp: _.toNumber(envelope.timestamp),
|
||||
lastJoinedTimestamp: toNumber(envelope.timestamp),
|
||||
// we just got readded. Consider the zombie list to have been cleared
|
||||
|
||||
zombies: [],
|
||||
|
@ -493,7 +547,7 @@ async function performIfValid(
|
|||
lastJoinedTimestamp = aYearAgo;
|
||||
}
|
||||
|
||||
const envelopeTimestamp = _.toNumber(envelope.timestamp);
|
||||
const envelopeTimestamp = toNumber(envelope.timestamp);
|
||||
if (envelopeTimestamp <= lastJoinedTimestamp) {
|
||||
window?.log?.warn(
|
||||
'Got a group update with an older timestamp than when we joined this group last time. Dropping it.'
|
||||
|
@ -513,26 +567,28 @@ async function performIfValid(
|
|||
// make sure the conversation with this user exist (even if it's just hidden)
|
||||
await getConversationController().getOrCreateAndWait(sender, ConversationTypeEnum.PRIVATE);
|
||||
|
||||
const moreRecentOrNah = await sentAtMoreRecentThanWrapper(envelopeTimestamp, 'UserGroupsConfig');
|
||||
const shouldNotApplyGroupChange = moreRecentOrNah === 'wrapper_more_recent';
|
||||
|
||||
if (groupUpdate.type === Type.NAME_CHANGE) {
|
||||
await handleClosedGroupNameChanged(envelope, groupUpdate, convo);
|
||||
await handleClosedGroupNameChanged(envelope, groupUpdate, convo, shouldNotApplyGroupChange);
|
||||
} else if (groupUpdate.type === Type.MEMBERS_ADDED) {
|
||||
await handleClosedGroupMembersAdded(envelope, groupUpdate, convo);
|
||||
await handleClosedGroupMembersAdded(envelope, groupUpdate, convo, shouldNotApplyGroupChange);
|
||||
} else if (groupUpdate.type === Type.MEMBERS_REMOVED) {
|
||||
await handleClosedGroupMembersRemoved(envelope, groupUpdate, convo);
|
||||
await handleClosedGroupMembersRemoved(envelope, groupUpdate, convo, shouldNotApplyGroupChange);
|
||||
} else if (groupUpdate.type === Type.MEMBER_LEFT) {
|
||||
await handleClosedGroupMemberLeft(envelope, convo);
|
||||
await handleClosedGroupMemberLeft(envelope, convo, shouldNotApplyGroupChange);
|
||||
} else if (groupUpdate.type === Type.ENCRYPTION_KEY_PAIR_REQUEST) {
|
||||
await removeFromCache(envelope);
|
||||
}
|
||||
// if you add a case here, remember to add it where performIfValid is called too.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleClosedGroupNameChanged(
|
||||
envelope: EnvelopePlus,
|
||||
groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage,
|
||||
convo: ConversationModel
|
||||
convo: ConversationModel,
|
||||
shouldOnlyAddUpdateMessage: boolean // set this to true to not apply the change to the convo itself, just add the update in the conversation
|
||||
) {
|
||||
// Only add update message if we have something to show
|
||||
const newName = groupUpdate.name;
|
||||
|
@ -546,9 +602,11 @@ async function handleClosedGroupNameChanged(
|
|||
convo,
|
||||
groupDiff,
|
||||
envelope.senderIdentity,
|
||||
_.toNumber(envelope.timestamp)
|
||||
toNumber(envelope.timestamp)
|
||||
);
|
||||
convo.set({ displayNameInProfile: newName });
|
||||
if (!shouldOnlyAddUpdateMessage) {
|
||||
convo.set({ displayNameInProfile: newName });
|
||||
}
|
||||
convo.updateLastMessage();
|
||||
await convo.commit();
|
||||
}
|
||||
|
@ -559,7 +617,8 @@ async function handleClosedGroupNameChanged(
|
|||
async function handleClosedGroupMembersAdded(
|
||||
envelope: EnvelopePlus,
|
||||
groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage,
|
||||
convo: ConversationModel
|
||||
convo: ConversationModel,
|
||||
shouldOnlyAddUpdateMessage: boolean // set this to true to not apply the change to the convo itself, just add the update in the conversation
|
||||
) {
|
||||
const { members: addedMembersBinary } = groupUpdate;
|
||||
const addedMembers = (addedMembersBinary || []).map(toHex);
|
||||
|
@ -602,10 +661,12 @@ async function handleClosedGroupMembersAdded(
|
|||
convo,
|
||||
groupDiff,
|
||||
envelope.senderIdentity,
|
||||
_.toNumber(envelope.timestamp)
|
||||
toNumber(envelope.timestamp)
|
||||
);
|
||||
|
||||
convo.set({ members });
|
||||
if (!shouldOnlyAddUpdateMessage) {
|
||||
convo.set({ members });
|
||||
}
|
||||
|
||||
convo.updateLastMessage();
|
||||
await convo.commit();
|
||||
|
@ -625,7 +686,8 @@ async function areWeAdmin(groupConvo: ConversationModel) {
|
|||
async function handleClosedGroupMembersRemoved(
|
||||
envelope: EnvelopePlus,
|
||||
groupUpdate: SignalService.DataMessage.ClosedGroupControlMessage,
|
||||
convo: ConversationModel
|
||||
convo: ConversationModel,
|
||||
shouldOnlyAddUpdateMessage: boolean // set this to true to not apply the change to the convo itself, just add the update in the conversation
|
||||
) {
|
||||
// Check that the admin wasn't removed
|
||||
const currentMembers = convo.get('members');
|
||||
|
@ -681,7 +743,7 @@ async function handleClosedGroupMembersRemoved(
|
|||
convo,
|
||||
groupDiff,
|
||||
envelope.senderIdentity,
|
||||
_.toNumber(envelope.timestamp)
|
||||
toNumber(envelope.timestamp)
|
||||
);
|
||||
convo.updateLastMessage();
|
||||
}
|
||||
|
@ -689,9 +751,10 @@ async function handleClosedGroupMembersRemoved(
|
|||
// Update the group
|
||||
const zombies = convo.get('zombies').filter(z => membersAfterUpdate.includes(z));
|
||||
|
||||
convo.set({ members: membersAfterUpdate });
|
||||
convo.set({ zombies });
|
||||
|
||||
if (!shouldOnlyAddUpdateMessage) {
|
||||
convo.set({ members: membersAfterUpdate });
|
||||
convo.set({ zombies });
|
||||
}
|
||||
await convo.commit();
|
||||
}
|
||||
await removeFromCache(envelope);
|
||||
|
@ -761,7 +824,11 @@ async function handleClosedGroupLeftOurself(groupId: string, envelope: EnvelopeP
|
|||
await removeFromCache(envelope);
|
||||
}
|
||||
|
||||
async function handleClosedGroupMemberLeft(envelope: EnvelopePlus, convo: ConversationModel) {
|
||||
async function handleClosedGroupMemberLeft(
|
||||
envelope: EnvelopePlus,
|
||||
convo: ConversationModel,
|
||||
shouldOnlyAddUpdateMessage: boolean // set this to true to not apply the change to the convo itself, just add the update in the conversation
|
||||
) {
|
||||
const sender = envelope.senderIdentity;
|
||||
const groupPublicKey = envelope.source;
|
||||
const didAdminLeave = convo.get('groupAdmins')?.includes(sender) || false;
|
||||
|
@ -778,7 +845,6 @@ async function handleClosedGroupMemberLeft(envelope: EnvelopePlus, convo: Conver
|
|||
const ourPubkey = UserUtils.getOurPubKeyStrFromCache();
|
||||
|
||||
// if the admin leaves, the group is disabled for everyone
|
||||
|
||||
if (didAdminLeave) {
|
||||
await handleClosedGroupAdminMemberLeft(groupPublicKey, envelope);
|
||||
return;
|
||||
|
@ -801,14 +867,16 @@ async function handleClosedGroupMemberLeft(envelope: EnvelopePlus, convo: Conver
|
|||
convo,
|
||||
groupDiff,
|
||||
envelope.senderIdentity,
|
||||
_.toNumber(envelope.timestamp)
|
||||
toNumber(envelope.timestamp)
|
||||
);
|
||||
convo.updateLastMessage();
|
||||
// if a user just left and we are the admin, we remove him right away for everyone by sending a MEMBERS_REMOVED message so no need to add him as a zombie
|
||||
if (oldMembers.includes(sender)) {
|
||||
addMemberToZombies(envelope, PubKey.cast(sender), convo);
|
||||
}
|
||||
convo.set('members', newMembers);
|
||||
if (!shouldOnlyAddUpdateMessage) {
|
||||
convo.set('members', newMembers);
|
||||
}
|
||||
|
||||
await convo.commit();
|
||||
|
||||
|
|
|
@ -146,6 +146,50 @@ async function mergeConfigsWithIncomingUpdates(
|
|||
}
|
||||
}
|
||||
|
||||
export function getSettingsKeyFromLibsessionWrapper(
|
||||
wrapperType: ConfigWrapperObjectTypes
|
||||
): string | null {
|
||||
switch (wrapperType) {
|
||||
case 'UserConfig':
|
||||
return SettingsKey.latestUserProfileEnvelopeTimestamp;
|
||||
case 'ContactsConfig':
|
||||
return SettingsKey.latestUserContactsEnvelopeTimestamp;
|
||||
case 'UserGroupsConfig':
|
||||
return SettingsKey.latestUserGroupEnvelopeTimestamp;
|
||||
case 'ConvoInfoVolatileConfig':
|
||||
return null; // we don't really care about the convo info volatile one
|
||||
default:
|
||||
try {
|
||||
assertUnreachable(
|
||||
wrapperType,
|
||||
`getSettingsKeyFromLibsessionWrapper unknown type: ${wrapperType}`
|
||||
);
|
||||
} catch (e) {
|
||||
window.log.warn('assertUnreachable:', e.message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function updateLibsessionLatestProcessedUserTimestamp(
|
||||
wrapperType: ConfigWrapperObjectTypes,
|
||||
latestEnvelopeTimestamp: number
|
||||
) {
|
||||
const settingsKey = getSettingsKeyFromLibsessionWrapper(wrapperType);
|
||||
if (!settingsKey) {
|
||||
return;
|
||||
}
|
||||
const currentLatestEnvelopeProcessed = Storage.get(settingsKey) || 0;
|
||||
|
||||
const newLatestProcessed = Math.max(
|
||||
latestEnvelopeTimestamp,
|
||||
isNumber(currentLatestEnvelopeProcessed) ? currentLatestEnvelopeProcessed : 0
|
||||
);
|
||||
if (newLatestProcessed !== currentLatestEnvelopeProcessed || currentLatestEnvelopeProcessed) {
|
||||
await Storage.put(settingsKey, newLatestProcessed);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUserProfileUpdate(result: IncomingConfResult): Promise<IncomingConfResult> {
|
||||
const updateUserInfo = await UserConfigWrapperActions.getUserInfo();
|
||||
if (!updateUserInfo) {
|
||||
|
@ -161,6 +205,17 @@ async function handleUserProfileUpdate(result: IncomingConfResult): Promise<Inco
|
|||
updateUserInfo.priority
|
||||
);
|
||||
|
||||
const settingsKey = SettingsKey.latestUserProfileEnvelopeTimestamp;
|
||||
const currentLatestEnvelopeProcessed = Storage.get(settingsKey) || 0;
|
||||
|
||||
const newLatestProcessed = Math.max(
|
||||
result.latestEnvelopeTimestamp,
|
||||
isNumber(currentLatestEnvelopeProcessed) ? currentLatestEnvelopeProcessed : 0
|
||||
);
|
||||
if (newLatestProcessed !== currentLatestEnvelopeProcessed) {
|
||||
await Storage.put(settingsKey, newLatestProcessed);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -715,6 +770,14 @@ async function processMergingResults(results: Map<ConfigWrapperObjectTypes, Inco
|
|||
}
|
||||
}
|
||||
const variant = LibSessionUtil.kindToVariant(kind);
|
||||
try {
|
||||
await updateLibsessionLatestProcessedUserTimestamp(
|
||||
variant,
|
||||
incomingResult.latestEnvelopeTimestamp
|
||||
);
|
||||
} catch (e) {
|
||||
window.log.error(`updateLibsessionLatestProcessedUserTimestamp failed with "${e.message}"`);
|
||||
}
|
||||
|
||||
if (incomingResult.needsDump) {
|
||||
// The config data had changes so regenerate the dump and save it
|
||||
|
@ -917,7 +980,7 @@ const handleClosedGroupsFromConfigLegacy = async (
|
|||
publicKey: c.publicKey,
|
||||
});
|
||||
try {
|
||||
await handleNewClosedGroup(envelope, groupUpdate);
|
||||
await handleNewClosedGroup(envelope, groupUpdate, true);
|
||||
} catch (e) {
|
||||
window?.log?.warn('failed to handle a new closed group from configuration message');
|
||||
}
|
||||
|
|
|
@ -12,7 +12,11 @@ import {
|
|||
deleteMessagesFromSwarmAndCompletelyLocally,
|
||||
deleteMessagesFromSwarmAndMarkAsDeletedLocally,
|
||||
} from '../interactions/conversations/unsendingInteractions';
|
||||
import { ConversationTypeEnum, READ_MESSAGE_STATE } from '../models/conversationAttributes';
|
||||
import {
|
||||
CONVERSATION_PRIORITIES,
|
||||
ConversationTypeEnum,
|
||||
READ_MESSAGE_STATE,
|
||||
} from '../models/conversationAttributes';
|
||||
import { findCachedBlindedMatchOrLookupOnAllServers } from '../session/apis/open_group_api/sogsv3/knownBlindedkeys';
|
||||
import { getConversationController } from '../session/conversations';
|
||||
import { concatUInt8Array, getSodiumRenderer } from '../session/crypto';
|
||||
|
@ -26,9 +30,10 @@ import { BlockedNumberController } from '../util';
|
|||
import { ReadReceipts } from '../util/readReceipts';
|
||||
import { Storage } from '../util/storage';
|
||||
import { handleCallMessage } from './callMessage';
|
||||
import { getAllCachedECKeyPair } from './closedGroups';
|
||||
import { getAllCachedECKeyPair, sentAtMoreRecentThanWrapper } from './closedGroups';
|
||||
import { ConfigMessageHandler } from './configMessage';
|
||||
import { ECKeyPair } from './keypairs';
|
||||
import { ContactsWrapperActions } from '../webworker/workers/browser/libsession_worker_interface';
|
||||
|
||||
export async function handleSwarmContentMessage(envelope: EnvelopePlus, messageHash: string) {
|
||||
try {
|
||||
|
@ -287,6 +292,38 @@ async function decrypt(envelope: EnvelopePlus): Promise<any> {
|
|||
}
|
||||
}
|
||||
|
||||
async function shouldDropIncomingPrivateMessage(sentAtTimestamp: number, envelope: EnvelopePlus) {
|
||||
// sentAtMoreRecentThanWrapper is going to be true, if the latest contact wrapper we processed was roughly more recent that this message timestamp
|
||||
const moreRecentOrNah = await sentAtMoreRecentThanWrapper(sentAtTimestamp, 'ContactsConfig');
|
||||
|
||||
if (moreRecentOrNah === 'wrapper_more_recent') {
|
||||
// we need to check if that conversation is already in the wrapper, and if yes
|
||||
try {
|
||||
const privateConvoInWrapper = await ContactsWrapperActions.get(envelope.source);
|
||||
if (
|
||||
!privateConvoInWrapper ||
|
||||
privateConvoInWrapper.priority <= CONVERSATION_PRIORITIES.hidden
|
||||
) {
|
||||
// the wrapper is more recent that this message and there is no such private conversation. Just drop this incoming message.
|
||||
window.log.info(
|
||||
`received message from contact ${envelope.source} which appears to be hidden/removed in our most recent libsession contactconfig, sentAt: ${sentAtTimestamp}. Dropping it`
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
window.log.info(
|
||||
`received message from contact ${envelope.source} which appears to NOT be hidden/removed in our most recent libsession contactconfig, sentAt: ${sentAtTimestamp}. `
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
window.log.warn(
|
||||
'ContactsWrapperActions.get in handleSwarmDataMessage failed with',
|
||||
e.message
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function shouldDropBlockedUserMessage(
|
||||
content: SignalService.Content,
|
||||
groupPubkey: string
|
||||
|
@ -373,6 +410,12 @@ export async function innerHandleSwarmContentMessage(
|
|||
// if this is a closed group message, envelope.senderIdentity is the sender's pubkey and envelope.source is the closed group's pubkey
|
||||
const isPrivateConversationMessage = !envelope.senderIdentity;
|
||||
|
||||
if (isPrivateConversationMessage) {
|
||||
if (await shouldDropIncomingPrivateMessage(sentAtTimestamp, envelope)) {
|
||||
return removeFromCache(envelope);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For a closed group message, this holds the conversation with that specific user outside of the closed group.
|
||||
* For a private conversation message, this is just the conversation with that user
|
||||
|
@ -396,7 +439,7 @@ export async function innerHandleSwarmContentMessage(
|
|||
|
||||
if (content.dataMessage) {
|
||||
// because typescript is funky with incoming protobufs
|
||||
if (content.dataMessage.profileKey && content.dataMessage.profileKey.length === 0) {
|
||||
if (isEmpty(content.dataMessage.profileKey)) {
|
||||
content.dataMessage.profileKey = null;
|
||||
}
|
||||
perfStart(`handleSwarmDataMessage-${envelope.id}`);
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
import { SignalService } from './../protobuf';
|
||||
import { removeFromCache } from './cache';
|
||||
import { EnvelopePlus } from './types';
|
||||
import { getEnvelopeId } from './common';
|
||||
import { EnvelopePlus } from './types';
|
||||
|
||||
import { PubKey } from '../session/types';
|
||||
import { handleMessageJob, toRegularMessage } from './queuedJob';
|
||||
import { isEmpty, isFinite, noop, omit, toNumber } from 'lodash';
|
||||
import { StringUtils, UserUtils } from '../session/utils';
|
||||
import { getConversationController } from '../session/conversations';
|
||||
import { handleClosedGroupControlMessage } from './closedGroups';
|
||||
import { Data } from '../../ts/data/data';
|
||||
import { ConversationModel } from '../models/conversation';
|
||||
import { getConversationController } from '../session/conversations';
|
||||
import { PubKey } from '../session/types';
|
||||
import { StringUtils, UserUtils } from '../session/utils';
|
||||
import { handleClosedGroupControlMessage } from './closedGroups';
|
||||
import { handleMessageJob, toRegularMessage } from './queuedJob';
|
||||
|
||||
import { ConversationTypeEnum } from '../models/conversationAttributes';
|
||||
import { MessageModel } from '../models/message';
|
||||
import {
|
||||
createSwarmMessageSentFromNotUs,
|
||||
createSwarmMessageSentFromUs,
|
||||
} from '../models/messageFactory';
|
||||
import { MessageModel } from '../models/message';
|
||||
import { isUsFromCache } from '../session/utils/User';
|
||||
import { toLogFormat } from '../types/attachments/Errors';
|
||||
import { ConversationTypeEnum } from '../models/conversationAttributes';
|
||||
import { Reactions } from '../util/reactions';
|
||||
import { Action, Reaction } from '../types/Reaction';
|
||||
import { ProfileManager } from '../session/profile_manager/ProfileManager';
|
||||
import { isUsFromCache } from '../session/utils/User';
|
||||
import { Action, Reaction } from '../types/Reaction';
|
||||
import { toLogFormat } from '../types/attachments/Errors';
|
||||
import { Reactions } from '../util/reactions';
|
||||
|
||||
function cleanAttachment(attachment: any) {
|
||||
return {
|
||||
|
@ -185,8 +185,6 @@ export async function handleSwarmDataMessage(
|
|||
if (isSyncedMessage && !isMe) {
|
||||
window?.log?.warn('Got a sync message from someone else than me. Dropping it.');
|
||||
return removeFromCache(envelope);
|
||||
} else if (isSyncedMessage) {
|
||||
// we should create the synTarget convo but I have no idea how to know if this is a private or closed group convo?
|
||||
}
|
||||
const convoIdToAddTheMessageTo = PubKey.removeTextSecurePrefixIfNeeded(
|
||||
isSyncedMessage ? cleanDataMessage.syncTarget : envelope.source
|
||||
|
@ -201,16 +199,16 @@ export async function handleSwarmDataMessage(
|
|||
typeOfConvo = ConversationTypeEnum.GROUP;
|
||||
}
|
||||
|
||||
window?.log?.info(
|
||||
`Handle dataMessage about convo ${convoIdToAddTheMessageTo} from user: ${convoIdOfSender}`
|
||||
);
|
||||
|
||||
// remove the prefix from the source object so this is correct for all other
|
||||
const convoToAddMessageTo = await getConversationController().getOrCreateAndWait(
|
||||
convoIdToAddTheMessageTo,
|
||||
typeOfConvo
|
||||
);
|
||||
|
||||
window?.log?.info(
|
||||
`Handle dataMessage about convo ${convoIdToAddTheMessageTo} from user: ${convoIdOfSender}`
|
||||
);
|
||||
// remove the prefix from the source object so this is correct for all other
|
||||
|
||||
// Check if we need to update any profile names
|
||||
if (
|
||||
!isMe &&
|
||||
|
|
|
@ -25,6 +25,7 @@ const forceNetworkDeletion = async (): Promise<Array<string> | null> => {
|
|||
return null;
|
||||
}
|
||||
const method = 'delete_all' as const;
|
||||
const namespace = 'all' as const;
|
||||
|
||||
try {
|
||||
const maliciousSnodes = await pRetry(
|
||||
|
@ -41,12 +42,12 @@ const forceNetworkDeletion = async (): Promise<Array<string> | null> => {
|
|||
async () => {
|
||||
const signOpts = await SnodeSignature.getSnodeSignatureParams({
|
||||
method,
|
||||
namespace: null,
|
||||
namespace,
|
||||
pubkey: userX25519PublicKey,
|
||||
});
|
||||
|
||||
const ret = await doSnodeBatchRequest(
|
||||
[{ method, params: signOpts }],
|
||||
[{ method, params: { ...signOpts, namespace } }],
|
||||
snodeToMakeRequestTo,
|
||||
10000,
|
||||
userX25519PublicKey
|
||||
|
@ -112,12 +113,18 @@ const forceNetworkDeletion = async (): Promise<Array<string> | null> => {
|
|||
return snodePubkey;
|
||||
}
|
||||
|
||||
const hashes = snodeJson.deleted as Array<string>;
|
||||
const deletedObj = snodeJson.deleted as Record<number, Array<string>>;
|
||||
const hashes: Array<string> = [];
|
||||
for (const key in deletedObj) {
|
||||
hashes.push(...deletedObj[key]);
|
||||
}
|
||||
const sortedHashes = hashes.sort();
|
||||
const signatureSnode = snodeJson.signature as string;
|
||||
// The signature format is ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] )
|
||||
const dataToVerify = `${userX25519PublicKey}${signOpts.timestamp}${hashes.join(
|
||||
''
|
||||
)}`;
|
||||
// The signature format is (with sortedHashes) ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] )
|
||||
const dataToVerify = `${userX25519PublicKey}${
|
||||
signOpts.timestamp
|
||||
}${sortedHashes.join('')}`;
|
||||
|
||||
const dataToVerifyUtf8 = StringUtils.encode(dataToVerify, 'utf8');
|
||||
const isValid = sodium.crypto_sign_verify_detached(
|
||||
fromBase64ToArray(signatureSnode),
|
||||
|
|
|
@ -101,6 +101,7 @@ export type StoreOnNodeParamsNoSig = Pick<
|
|||
|
||||
export type DeleteFromNodeWithTimestampParams = {
|
||||
timestamp: string | number;
|
||||
namespace: number | null | 'all';
|
||||
} & DeleteSigParameters;
|
||||
export type DeleteByHashesFromNodeParams = { messages: Array<string> } & DeleteSigParameters;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { last, orderBy } from 'lodash';
|
||||
import { assertUnreachable } from '../../../types/sqlSharedTypes';
|
||||
import { PickEnum } from '../../../types/Enums';
|
||||
|
||||
export enum SnodeNamespaces {
|
||||
/**
|
||||
|
@ -36,10 +37,6 @@ export enum SnodeNamespaces {
|
|||
// ClosedGroupInfo = 1,
|
||||
}
|
||||
|
||||
type PickEnum<T, K extends T> = {
|
||||
[P in keyof K]: P extends K ? P : never;
|
||||
};
|
||||
|
||||
export type SnodeNamespacesGroup = PickEnum<
|
||||
SnodeNamespaces,
|
||||
SnodeNamespaces.ClosedGroupMessage // | SnodeNamespaces.ClosedGroupInfo
|
||||
|
|
|
@ -54,7 +54,7 @@ async function getSnodeSignatureByHashesParams({
|
|||
|
||||
async function getSnodeSignatureParams(params: {
|
||||
pubkey: string;
|
||||
namespace: number | null;
|
||||
namespace: number | null | 'all'; // 'all' can be used to clear all namespaces (during account deletion)
|
||||
method: 'retrieve' | 'store' | 'delete_all';
|
||||
}): Promise<SnodeSignatureResult> {
|
||||
const ourEd25519Key = await UserUtils.getUserED25519KeyPair();
|
||||
|
@ -69,10 +69,12 @@ async function getSnodeSignatureParams(params: {
|
|||
|
||||
const signatureTimestamp = GetNetworkTime.getNowWithNetworkOffset();
|
||||
|
||||
const withoutNamespace = `${params.method}${signatureTimestamp}`;
|
||||
const withNamespace = `${params.method}${namespace}${signatureTimestamp}`;
|
||||
const verificationData =
|
||||
namespace === 0
|
||||
? StringUtils.encode(`${params.method}${signatureTimestamp}`, 'utf8')
|
||||
: StringUtils.encode(`${params.method}${namespace}${signatureTimestamp}`, 'utf8');
|
||||
? StringUtils.encode(withoutNamespace, 'utf8')
|
||||
: StringUtils.encode(withNamespace, 'utf8');
|
||||
|
||||
const message = new Uint8Array(verificationData);
|
||||
|
||||
|
|
|
@ -393,8 +393,8 @@ async function generateAndSendNewEncryptionKeyPair(
|
|||
return;
|
||||
}
|
||||
|
||||
const ourNumber = UserUtils.getOurPubKeyFromCache();
|
||||
if (!groupConvo.get('groupAdmins')?.includes(ourNumber.key)) {
|
||||
const ourNumber = UserUtils.getOurPubKeyStrFromCache();
|
||||
if (!groupConvo.get('groupAdmins')?.includes(ourNumber)) {
|
||||
window?.log?.warn('generateAndSendNewEncryptionKeyPair: cannot send it as a non admin');
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ export type ByteKeyPair = {
|
|||
|
||||
/**
|
||||
* Check if this pubkey is us, using the cache.
|
||||
* This does not check for us blinded. To check for us or us blinded, use isUsAnySogsFromCache()
|
||||
* Throws an error if our pubkey is not set
|
||||
*/
|
||||
export function isUsFromCache(pubKey: string | PubKey | undefined): boolean {
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
} from '../PersistedJob';
|
||||
import { ReleasedFeatures } from '../../../../util/releaseFeature';
|
||||
import { allowOnlyOneAtATime } from '../../Promise';
|
||||
import { isSignInByLinking } from '../../../../util/storage';
|
||||
|
||||
const defaultMsBetweenRetries = 30000; // a long time between retries, to avoid running multiple jobs at the same time, when one was postponed at the same time as one already planned (5s)
|
||||
const defaultMaxAttempts = 2;
|
||||
|
@ -284,6 +285,11 @@ class ConfigurationSyncJob extends PersistedJob<ConfigurationSyncPersistedData>
|
|||
* A ConfigurationSyncJob can only be added if there is none of the same type queued already.
|
||||
*/
|
||||
async function queueNewJobIfNeeded() {
|
||||
if (isSignInByLinking()) {
|
||||
window.log.info('NOT Scheduling ConfSyncJob: as we are linking a device');
|
||||
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!lastRunConfigSyncJobTimestamp ||
|
||||
lastRunConfigSyncJobTimestamp < Date.now() - defaultMsBetweenRetries
|
||||
|
|
|
@ -71,7 +71,6 @@ async function insertContactFromDBIntoWrapperAndRefresh(id: string): Promise<voi
|
|||
priority,
|
||||
expirationTimerSeconds,
|
||||
});
|
||||
|
||||
try {
|
||||
window.log.debug('inserting into contact wrapper: ', JSON.stringify(wrapperContact));
|
||||
await ContactsWrapperActions.set(wrapperContact);
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
// tslint:disable: no-implicit-dependencies max-func-body-length no-unused-expression
|
||||
|
||||
import chai from 'chai';
|
||||
import { describe } from 'mocha';
|
||||
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
chai.use(chaiAsPromised as any);
|
||||
|
||||
// tslint:disable-next-line: max-func-body-length
|
||||
describe('ClosedGroupUpdates', () => {
|
||||
// const ourDevice = TestUtils.generateFakePubKey();
|
||||
// const ourNumber = ourDevice.key;
|
||||
// const groupId = TestUtils.generateFakePubKey().key;
|
||||
// const members = TestUtils.generateFakePubKeys(10);
|
||||
// const sender = members[3].key;
|
||||
// const getConvo = sandbox.stub(ConversationController, 'get');
|
||||
// beforeEach(async () => {
|
||||
// // Utils Stubs
|
||||
// sandbox.stub(UserUtils, 'getCurrentDevicePubKey').resolves(ourNumber);
|
||||
// });
|
||||
// afterEach(() => {
|
||||
// sandbox.restore();
|
||||
// });
|
||||
// describe('handleClosedGroupControlMessage', () => {
|
||||
// describe('performIfValid', () => {
|
||||
// it('does not perform if convo does not exist', async () => {
|
||||
// const envelope = generateEnvelopePlusClosedGroup(groupId, sender);
|
||||
// const groupUpdate = generateGroupUpdateNameChange(groupId);
|
||||
// getConvo.returns(undefined as any);
|
||||
// await handleClosedGroupControlMessage(envelope, groupUpdate);
|
||||
// });
|
||||
// });
|
||||
// // describe('handleClosedGroupNameChanged', () => {
|
||||
// // it('does not trigger an update of the group if the name is the same', async () => {
|
||||
// // const envelope = generateEnvelopePlusClosedGroup(groupId, sender);
|
||||
// // const groupUpdate = generateGroupUpdateNameChange(groupId);
|
||||
// // await handleClosedGroupControlMessage(envelope, groupUpdate);
|
||||
// // });
|
||||
// // });
|
||||
// });
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
export type PickEnum<T, K extends T> = {
|
||||
[P in keyof K]: P extends K ? P : never;
|
||||
};
|
Loading…
Reference in New Issue