fix: use at least 2 hashes for the update_expiries too

until the storage server release is live we need this workaround
pull/2940/head
Audric Ackermann 1 year ago
parent 744283fc56
commit f6cd12d599

@ -1009,6 +1009,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
...expireUpdate, ...expireUpdate,
groupId: this.get('id'), groupId: this.get('id'),
}; };
// NOTE: we agreed that outgoing ExpirationTimerUpdate **for groups** are not expiring.
expireUpdate.expirationType = 'unknown';
expireUpdate.expireTimer = 0;
const expirationTimerMessage = new ExpirationTimerUpdateMessage(expireUpdateForGroup); const expirationTimerMessage = new ExpirationTimerUpdateMessage(expireUpdateForGroup);
@ -1834,7 +1837,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
identifier: id, identifier: id,
timestamp: sentAt, timestamp: sentAt,
attachments, attachments,
expirationType: message.getExpirationType() ?? 'unknown', expirationType: message.getExpirationType() ?? 'unknown', // Note we assume that the caller used a setting allowed for that conversation when building it. Here we just send it.
expireTimer: message.getExpireTimerSeconds(), expireTimer: message.getExpireTimerSeconds(),
preview: preview ? [preview] : [], preview: preview ? [preview] : [],
quote, quote,

@ -231,6 +231,9 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
public isDataExtractionNotification() { public isDataExtractionNotification() {
return !!this.get('dataExtractionNotification'); return !!this.get('dataExtractionNotification');
} }
public isCallNotification() {
return !!this.get('callNotificationType');
}
public getNotificationText() { public getNotificationText() {
let description = this.getDescription(); let description = this.getDescription();
@ -488,11 +491,14 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
// some incoming legacy group updates are outgoing, but when synced to our other devices have just the received_at field set. // some incoming legacy group updates are outgoing, but when synced to our other devices have just the received_at field set.
// when that is the case, we don't want to render the spinning 'sending' state // when that is the case, we don't want to render the spinning 'sending' state
if (this.get('received_at')) { if (
(this.isExpirationTimerUpdate() || this.isDataExtractionNotification()) &&
this.get('received_at')
) {
return undefined; return undefined;
} }
if (this.isDataExtractionNotification() || this.get('callNotificationType')) { if (this.isDataExtractionNotification() || this.isCallNotification()) {
return undefined; return undefined;
} }
@ -1339,7 +1345,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
getConversationController().getContactProfileNameOrShortenedPubKey(dataExtraction.source), getConversationController().getContactProfileNameOrShortenedPubKey(dataExtraction.source),
]); ]);
} }
if (this.get('callNotificationType')) { if (this.isCallNotification()) {
const displayName = getConversationController().getContactProfileNameOrShortenedPubKey( const displayName = getConversationController().getContactProfileNameOrShortenedPubKey(
this.get('conversationId') this.get('conversationId')
); );

@ -134,7 +134,7 @@ export type DeleteFromNodeSubRequest = {
export type UpdateExpireNodeParams = { export type UpdateExpireNodeParams = {
pubkey: string; pubkey: string;
pubkey_ed25519: string; pubkey_ed25519: string;
messages: Array<string>; messages: Array<string>; // Must have at least 2 arguments until the next storage server release (check fakeHash)
expiry: number; expiry: number;
signature: string; signature: string;
extend?: boolean; extend?: boolean;
@ -159,6 +159,9 @@ export type GetExpiriesFromNodeSubRequest = {
params: GetExpiriesNodeParams; params: GetExpiriesNodeParams;
}; };
// Until the next storage server release is released, we need to have at least 2 hashes in the list for the `get_expiries` AND for the `update_expiries`
export const fakeHash = '///////////////////////////////////////////';
export type OxendSubRequest = OnsResolveSubRequest | GetServiceNodesSubRequest; export type OxendSubRequest = OnsResolveSubRequest | GetServiceNodesSubRequest;
export type SnodeApiSubRequests = export type SnodeApiSubRequests =

@ -21,6 +21,7 @@ import {
MAX_SUBREQUESTS_COUNT, MAX_SUBREQUESTS_COUNT,
UpdateExpiryOnNodeSubRequest, UpdateExpiryOnNodeSubRequest,
WithShortenOrExtend, WithShortenOrExtend,
fakeHash,
} from './SnodeRequestTypes'; } from './SnodeRequestTypes';
import { doSnodeBatchRequest } from './batchRequest'; import { doSnodeBatchRequest } from './batchRequest';
import { getSwarmFor } from './snodePool'; import { getSwarmFor } from './snodePool';
@ -81,7 +82,7 @@ export async function verifyExpireMsgsResponseSignature({
export type ExpireRequestResponseResults = Record< export type ExpireRequestResponseResults = Record<
string, string,
{ hashes: Array<string>; expiry: number } { hashes: Array<string>; expiry: number; unchangedHashes: Record<string, number> }
>; >;
export async function processExpireRequestResponse( export async function processExpireRequestResponse(
@ -142,7 +143,7 @@ export async function processExpireRequestResponse(
); );
continue; continue;
} }
results[nodeKey] = { hashes: updatedHashes, expiry }; results[nodeKey] = { hashes: updatedHashes, expiry, unchangedHashes: unchangedHashes ?? {} };
} }
return results; return results;
@ -212,17 +213,32 @@ async function updateExpiryOnNodes(
messageHashes: expirationResult.hashes, messageHashes: expirationResult.hashes,
updatedExpiryMs: expirationResult.expiry, updatedExpiryMs: expirationResult.expiry,
}); });
if (!isEmpty(expirationResult.unchangedHashes)) {
const unchanged = Object.entries(expirationResult.unchangedHashes);
unchanged.forEach(m => {
changesValid.push({
messageHashes: [m[0]],
updatedExpiryMs: m[1],
});
});
}
} }
const hashesRequestedButNotInResults = difference( const hashesRequestedButNotInResults = difference(
flatten(expireRequests.map(m => m.params.messages)), flatten(expireRequests.map(m => m.params.messages)),
flatten(changesValid.map(c => c.messageHashes)) [...flatten(changesValid.map(c => c.messageHashes)), fakeHash]
); );
if (!isEmpty(hashesRequestedButNotInResults)) { if (!isEmpty(hashesRequestedButNotInResults)) {
const now = Date.now();
window.log.debug(
'messageHashes not found on swarm, considering them expired now():',
hashesRequestedButNotInResults,
now
);
// we requested hashes which are not part of the result. They most likely expired already so let's mark those messages as expiring now. // we requested hashes which are not part of the result. They most likely expired already so let's mark those messages as expiring now.
changesValid.push({ changesValid.push({
messageHashes: hashesRequestedButNotInResults, messageHashes: hashesRequestedButNotInResults,
updatedExpiryMs: Date.now(), updatedExpiryMs: now,
}); });
} }
@ -351,6 +367,14 @@ function groupMsgByExpiry(expiringDetails: ExpiringDetails) {
} }
groupedBySameExpiry[expiryStr].push(messageHash); groupedBySameExpiry[expiryStr].push(messageHash);
} }
Object.keys(groupedBySameExpiry).forEach(k => {
if (groupedBySameExpiry[k].length === 1) {
// We need to have at least 2 hashes until the next storage server release
groupedBySameExpiry[k].push(fakeHash);
}
});
return groupedBySameExpiry; return groupedBySameExpiry;
} }

@ -5,7 +5,7 @@ import { Snode } from '../../../data/data';
import { UserUtils } from '../../utils'; import { UserUtils } from '../../utils';
import { EmptySwarmError } from '../../utils/errors'; import { EmptySwarmError } from '../../utils/errors';
import { SeedNodeAPI } from '../seed_node_api'; import { SeedNodeAPI } from '../seed_node_api';
import { GetExpiriesFromNodeSubRequest } from './SnodeRequestTypes'; import { GetExpiriesFromNodeSubRequest, fakeHash } from './SnodeRequestTypes';
import { doSnodeBatchRequest } from './batchRequest'; import { doSnodeBatchRequest } from './batchRequest';
import { GetNetworkTime } from './getNetworkTime'; import { GetNetworkTime } from './getNetworkTime';
import { getSwarmFor } from './snodePool'; import { getSwarmFor } from './snodePool';
@ -149,7 +149,7 @@ export async function buildGetExpiriesRequest({
export async function getExpiriesFromSnode({ messageHashes }: GetExpiriesFromSnodeProps) { export async function getExpiriesFromSnode({ messageHashes }: GetExpiriesFromSnodeProps) {
// FIXME There is a bug in the snode code that requires at least 2 messages to be requested. Will be fixed in next storage server release // FIXME There is a bug in the snode code that requires at least 2 messages to be requested. Will be fixed in next storage server release
if (messageHashes.length === 1) { if (messageHashes.length === 1) {
messageHashes.push('fakehash'); messageHashes.push(fakeHash);
} }
const ourPubKey = UserUtils.getOurPubKeyStrFromCache(); const ourPubKey = UserUtils.getOurPubKeyStrFromCache();

@ -26,7 +26,6 @@ import { OpenGroupUtils } from '../apis/open_group_api/utils';
import { getSwarmPollingInstance } from '../apis/snode_api'; import { getSwarmPollingInstance } from '../apis/snode_api';
import { GetNetworkTime } from '../apis/snode_api/getNetworkTime'; import { GetNetworkTime } from '../apis/snode_api/getNetworkTime';
import { SnodeNamespaces } from '../apis/snode_api/namespaces'; import { SnodeNamespaces } from '../apis/snode_api/namespaces';
import { DisappearingMessages } from '../disappearing_messages';
import { ClosedGroupMemberLeftMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupMemberLeftMessage'; import { ClosedGroupMemberLeftMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupMemberLeftMessage';
import { UserUtils } from '../utils'; import { UserUtils } from '../utils';
import { ConfigurationSync } from '../utils/job_runners/jobs/ConfigurationSyncJob'; import { ConfigurationSync } from '../utils/job_runners/jobs/ConfigurationSyncJob';
@ -497,12 +496,8 @@ async function leaveClosedGroup(groupId: string, fromSyncMessage: boolean) {
const ourLeavingMessage = new ClosedGroupMemberLeftMessage({ const ourLeavingMessage = new ClosedGroupMemberLeftMessage({
timestamp: networkTimestamp, timestamp: networkTimestamp,
groupId, groupId,
expirationType: DisappearingMessages.changeToDisappearingMessageType( expirationType: null, // we keep that one **not** expiring
convo, expireTimer: null,
convo.getExpireTimer(),
convo.getExpirationMode()
),
expireTimer: convo.getExpireTimer(),
}); });
window?.log?.info(`We are leaving the group ${groupId}. Sending our leaving message.`); window?.log?.info(`We are leaving the group ${groupId}. Sending our leaving message.`);

@ -188,7 +188,7 @@ function createInvitePromises(
admins, admins,
keypair: encryptionKeyPair, keypair: encryptionKeyPair,
timestamp: Date.now(), timestamp: Date.now(),
expirationType: null, // Note: we do not make those messages expire as we want them available as much as possible on the swarm of the recipient expirationType: null, // we keep that one **not** expiring
expireTimer: 0, expireTimer: 0,
}; };
const message = new ClosedGroupNewMessage(messageParams); const message = new ClosedGroupNewMessage(messageParams);

@ -582,6 +582,7 @@ async function updateMessageExpiriesOnSwarm(messages: Array<MessageModel>) {
const newTTLs = await expireMessagesOnSnode(expiringDetails, { shortenOrExtend: 'shorten' }); const newTTLs = await expireMessagesOnSnode(expiringDetails, { shortenOrExtend: 'shorten' });
const updatedMsgModels: Array<MessageModel> = []; const updatedMsgModels: Array<MessageModel> = [];
window.log.debug('updateMessageExpiriesOnSwarm newTTLs: ', newTTLs);
newTTLs.forEach(m => { newTTLs.forEach(m => {
const message = messages.find(model => model.getMessageHash() === m.messageHash); const message = messages.find(model => model.getMessageHash() === m.messageHash);
if (!message) { if (!message) {

@ -290,12 +290,8 @@ async function sendNewName(convo: ConversationModel, name: string, messageId: st
groupId, groupId,
identifier: messageId, identifier: messageId,
name, name,
expirationType: DisappearingMessages.changeToDisappearingMessageType( expirationType: null, // we keep that one **not** expiring
convo, expireTimer: 0,
convo.getExpireTimer(),
convo.getExpirationMode()
),
expireTimer: convo.getExpireTimer(),
}); });
await getMessageQueue().sendToGroup({ await getMessageQueue().sendToGroup({
message: nameChangeMessage, message: nameChangeMessage,
@ -304,7 +300,7 @@ async function sendNewName(convo: ConversationModel, name: string, messageId: st
} }
async function sendAddedMembers( async function sendAddedMembers(
convo: ConversationModel, _convo: ConversationModel,
addedMembers: Array<string>, addedMembers: Array<string>,
messageId: string, messageId: string,
groupUpdate: GroupInfo groupUpdate: GroupInfo
@ -324,20 +320,14 @@ async function sendAddedMembers(
} }
const encryptionKeyPair = ECKeyPair.fromHexKeyPair(hexEncryptionKeyPair); const encryptionKeyPair = ECKeyPair.fromHexKeyPair(hexEncryptionKeyPair);
const expirationMode = convo.getExpirationMode() || 'off';
const existingExpireTimer = convo.getExpireTimer() || 0;
// Send the Added Members message to the group (only members already in the group will get it) // Send the Added Members message to the group (only members already in the group will get it)
const closedGroupControlMessage = new ClosedGroupAddedMembersMessage({ const closedGroupControlMessage = new ClosedGroupAddedMembersMessage({
timestamp: Date.now(), timestamp: Date.now(),
groupId, groupId,
addedMembers, addedMembers,
identifier: messageId, identifier: messageId,
expirationType: DisappearingMessages.changeToDisappearingMessageType( expirationType: null, // we keep that one **not** expiring
convo, expireTimer: 0,
convo.getExpireTimer(),
convo.getExpirationMode()
),
expireTimer: convo.getExpireTimer(),
}); });
await getMessageQueue().sendToGroup({ await getMessageQueue().sendToGroup({
message: closedGroupControlMessage, message: closedGroupControlMessage,
@ -353,12 +343,8 @@ async function sendAddedMembers(
members, members,
keypair: encryptionKeyPair, keypair: encryptionKeyPair,
identifier: messageId || uuidv4(), identifier: messageId || uuidv4(),
expirationType: DisappearingMessages.changeToDisappearingMessageType( expirationType: null, // we keep that one **not** expiring
convo, expireTimer: 0,
existingExpireTimer,
expirationMode
),
expireTimer: existingExpireTimer,
}); });
const promises = addedMembers.map(async m => { const promises = addedMembers.map(async m => {
@ -401,12 +387,8 @@ export async function sendRemovedMembers(
groupId, groupId,
removedMembers, removedMembers,
identifier: messageId, identifier: messageId,
expirationType: DisappearingMessages.changeToDisappearingMessageType( expirationType: null, // we keep that one **not** expiring
convo, expireTimer: 0,
convo.getExpireTimer(),
convo.getExpirationMode()
),
expireTimer: convo.getExpireTimer(),
}); });
// Send the group update, and only once sent, generate and distribute a new encryption key pair if needed // Send the group update, and only once sent, generate and distribute a new encryption key pair if needed
await getMessageQueue().sendToGroup({ await getMessageQueue().sendToGroup({
@ -467,8 +449,8 @@ async function generateAndSendNewEncryptionKeyPair(
groupId: toHex(groupId), groupId: toHex(groupId),
timestamp: GetNetworkTime.getNowWithNetworkOffset(), timestamp: GetNetworkTime.getNowWithNetworkOffset(),
encryptedKeyPairs: wrappers, encryptedKeyPairs: wrappers,
expirationType: null, // we keep that one **not** expiring (not rendered in the clients, and we need it to be as available as possible on the swarm) expirationType: null, // we keep that one **not** expiring
expireTimer: null, expireTimer: 0,
}); });
distributingClosedGroupEncryptionKeyPairs.set(toHex(groupId), newKeyPair); distributingClosedGroupEncryptionKeyPairs.set(toHex(groupId), newKeyPair);

@ -1,7 +1,10 @@
import chai, { expect } from 'chai'; import chai, { expect } from 'chai';
import chaiAsPromised from 'chai-as-promised'; import chaiAsPromised from 'chai-as-promised';
import Sinon from 'sinon'; import Sinon from 'sinon';
import { GetExpiriesFromNodeSubRequest } from '../../../../session/apis/snode_api/SnodeRequestTypes'; import {
GetExpiriesFromNodeSubRequest,
fakeHash,
} from '../../../../session/apis/snode_api/SnodeRequestTypes';
import { import {
GetExpiriesFromSnodeProps, GetExpiriesFromSnodeProps,
GetExpiriesRequestResponseResults, GetExpiriesRequestResponseResults,
@ -89,7 +92,7 @@ describe('GetExpiriesRequest', () => {
targetNode: generateFakeSnode(), targetNode: generateFakeSnode(),
expiries: { 'FLTUh/C/6E+sWRgNtrqWPXhQqKlIrpHVKJJtZsBMWKw': 1696983251624 }, expiries: { 'FLTUh/C/6E+sWRgNtrqWPXhQqKlIrpHVKJJtZsBMWKw': 1696983251624 },
// FIXME There is a bug in the snode code that requires at least 2 messages to be requested. Will be fixed in next storage server release // FIXME There is a bug in the snode code that requires at least 2 messages to be requested. Will be fixed in next storage server release
messageHashes: ['FLTUh/C/6E+sWRgNtrqWPXhQqKlIrpHVKJJtZsBMWKw', 'fakehash'], messageHashes: ['FLTUh/C/6E+sWRgNtrqWPXhQqKlIrpHVKJJtZsBMWKw', fakeHash],
}; };
it('returns valid results if the response is valid', async () => { it('returns valid results if the response is valid', async () => {

@ -24,8 +24,8 @@ const LEVELS: Record<number, string> = {
// Backwards-compatible logging, simple strings and no level (defaulted to INFO) // Backwards-compatible logging, simple strings and no level (defaulted to INFO)
function now() { function now() {
const date = new Date(); const current = Date.now();
return date.toJSON(); return `${current}`;
} }
// To avoid [Object object] in our log since console.log handles non-strings smoothly // To avoid [Object object] in our log since console.log handles non-strings smoothly

Loading…
Cancel
Save