diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index b8dc201e6..fa8dd4739 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -119,10 +119,10 @@ import { import { DisappearingMessageConversationType, - isLegacyDisappearingModeEnabled, resolveLegacyDisappearingMode, } from '../util/expiringMessages'; import { markAttributesAsReadIfNeeded } from './messageFactory'; +import { ReleasedFeatures } from '../util/releaseFeature'; type InMemoryConvoInfos = { mentionedUs: boolean; @@ -837,16 +837,18 @@ export class ConversationModel extends Backbone.Model { if ( this.get('lastDisappearingMessageChangeTimestamp') > lastDisappearingMessageChangeTimestamp ) { - window.log.info('updateExpireTimer() This is an outdated disappearing message setting'); + window.log.info('WIP: updateExpireTimer() This is an outdated disappearing message setting'); return; } + // NOTE: We don' mind if the message is the same, we still want to update the conversation because we want to show visible control messages we receive an ExpirationTimerUpdate if ( + fromConfigMessage && isEqual(expirationType, this.get('expirationType')) && isEqual(expireTimer, this.get('expireTimer')) ) { window.log.info( - 'updateExpireTimer() Dropping ExpireTimerUpdate message as we already have the same one set.' + 'WIP: updateExpireTimer() Dropping ExpireTimerUpdate message as we already have the same one set.' ); return; } @@ -856,7 +858,8 @@ export class ConversationModel extends Backbone.Model { // Note the legacy type should only be in the UI, it should change the the conversation type default before we send if (expirationType === 'legacy') { - expirationType = resolveLegacyDisappearingMode(this); + expirationType = resolveLegacyDisappearingMode(this, expireTimer); + window.log.debug(`WIP: resolving legacy disappearing mode to ${expirationType}`); } // When we add a disappearing messages notification to the conversation, we want it @@ -892,10 +895,12 @@ export class ConversationModel extends Backbone.Model { fromSync, }, // TODO legacy messages support will be removed in a future release - expirationType: isLegacyDisappearingModeEnabled(expirationType) - ? undefined - : expirationType, - expireTimer: isLegacyDisappearingModeEnabled(expirationType) ? undefined : expireTimer, + expirationType: ReleasedFeatures.isDisappearMessageV2FeatureReleasedCached() + ? expirationType + : undefined, + expireTimer: ReleasedFeatures.isDisappearMessageV2FeatureReleasedCached() + ? expireTimer + : undefined, }; if (!message) { @@ -929,6 +934,9 @@ export class ConversationModel extends Backbone.Model { // if change was made remotely, don't send it to the contact/group if (receivedAt || fromSync || fromConfigMessage) { + window.log.debug( + `WIP: updateExpireTimer() Not sending an ExpireTimerUpdate message because the change was made remotely receivedAt:${receivedAt} fromSync:${fromSync} fromConfigMessage:${fromConfigMessage} ` + ); return; } @@ -942,13 +950,17 @@ export class ConversationModel extends Backbone.Model { if (this.isMe()) { // TODO Check that the args are correct - // This might be happening too late in the message pipeline. Maybe should be moved to handleExpirationTimerUpdateNoCommit() if (expireUpdate.expirationType === 'deleteAfterRead') { window.log.info('Note to Self messages cannot be delete after read!'); return; } const expirationTimerMessage = new ExpirationTimerUpdateMessage(expireUpdate); + window.log.debug( + `WIP: Sending ExpirationTimerUpdate message to Note to Self expirationTimerMessage:${JSON.stringify( + expirationTimerMessage + )}` + ); await message?.sendSyncMessageOnly(expirationTimerMessage); return; } diff --git a/ts/models/message.ts b/ts/models/message.ts index 68d704eda..d310cc01b 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -976,7 +976,7 @@ export class MessageModel extends Backbone.Model { } public async sendSyncMessageOnly(contentMessage: ContentMessage) { - const now = Date.now(); + const now = GetNetworkTime.getNowWithNetworkOffset(); this.set({ sent_to: [UserUtils.getOurPubKeyStrFromCache()], @@ -1015,16 +1015,16 @@ export class MessageModel extends Backbone.Model { dataMessage.flags === SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE) ); + const expirationTimer = isLegacyDisappearingDataMessage + ? Number(dataMessage.expireTimer) + : content.expirationTimer; + let expirationType: DisappearingMessageType = DisappearingMessageMode[content.expirationType]; if (isLegacyDisappearingDataMessage) { - expirationType = resolveLegacyDisappearingMode(conversation); + expirationType = resolveLegacyDisappearingMode(conversation, expirationTimer); } - const expirationTimer = isLegacyDisappearingDataMessage - ? Number(dataMessage.expireTimer) - : content.expirationTimer; - const lastDisappearingMessageChangeTimestamp = content.lastDisappearingMessageChangeTimestamp ? Number(content.lastDisappearingMessageChangeTimestamp) : undefined; diff --git a/ts/receiver/configMessage.ts b/ts/receiver/configMessage.ts index 8b2c46a59..67039b719 100644 --- a/ts/receiver/configMessage.ts +++ b/ts/receiver/configMessage.ts @@ -18,7 +18,7 @@ import { getOpenGroupV2ConversationId } from '../session/apis/open_group_api/uti import { getSwarmPollingInstance } from '../session/apis/snode_api'; import { getConversationController } from '../session/conversations'; import { IncomingMessage } from '../session/messages/incoming/IncomingMessage'; -import { ProfileManager } from '../session/profile_manager/ProfileManager'; +import { Profile, ProfileManager } from '../session/profile_manager/ProfileManager'; import { PubKey } from '../session/types'; import { StringUtils, UserUtils } from '../session/utils'; import { toHex } from '../session/utils/String'; @@ -192,6 +192,10 @@ async function updateLibsessionLatestProcessedUserTimestamp( } } +/** + * NOTE When adding new properties to the wrapper, don't update the conversation model here because the merge has not been done yet. + * Instead you will need to updateOurProfileLegacyOrViaLibSession() to support them + */ async function handleUserProfileUpdate(result: IncomingConfResult): Promise { const updateUserInfo = await UserConfigWrapperActions.getUserInfo(); if (!updateUserInfo) { @@ -206,14 +210,52 @@ async function handleUserProfileUpdate(result: IncomingConfResult): Promise 0 + ? 'deleteAfterSend' + : 'off' + ); + changes = true; + window.log.debug( + `WIP: [userProfileWrapper] updating disappearing messages to`, + wrapperNoteToSelfExpirySeconds && wrapperNoteToSelfExpirySeconds > 0 + ? 'deleteAfterSend' + : 'off', + ' ', + wrapperNoteToSelfExpirySeconds + ); + } + + // make sure to write the changes to the database now as the `AvatarDownloadJob` triggered by updateOurProfileLegacyOrViaLibSession might take some time before getting run + if (changes) { + await ourConvo.commit(); + } + } const settingsKey = SettingsKey.latestUserProfileEnvelopeTimestamp; const currentLatestEnvelopeProcessed = Storage.get(settingsKey) || 0; @@ -867,14 +909,19 @@ async function handleConfigMessagesViaLibSession( await processMergingResults(incomingMergeResult); } -async function updateOurProfileLegacyOrViaLibSession( - sentAt: number, - displayName: string, - profileUrl: string | null, - profileKey: Uint8Array | null, - priority: number | null // passing null means to not update the priority at all (used for legacy config message for now) -) { - await ProfileManager.updateOurProfileSync(displayName, profileUrl, profileKey, priority); +async function updateOurProfileLegacyOrViaLibSession({ + sentAt, + displayName, + profileUrl, + profileKey, + priority, +}: Profile & { sentAt: number }) { + await ProfileManager.updateOurProfileSync({ + displayName, + profileUrl, + profileKey, + priority, + }); await setLastProfileUpdateTimestamp(toNumber(sentAt)); // do not trigger a signin by linking if the display name is empty @@ -901,13 +948,13 @@ async function handleOurProfileUpdateLegacy( ); const { profileKey, profilePicture, displayName } = configMessage; - await updateOurProfileLegacyOrViaLibSession( - toNumber(sentAt), + await updateOurProfileLegacyOrViaLibSession({ + sentAt: toNumber(sentAt), displayName, - profilePicture, + profileUrl: profilePicture, profileKey, - null // passing null to say do not the prioroti, as we do not get one from the legacy config message - ); + priority: null, // passing null to say do not set the priority, as we do not get one from the legacy config message + }); } } diff --git a/ts/receiver/dataMessage.ts b/ts/receiver/dataMessage.ts index 3b13090de..526cc9d1d 100644 --- a/ts/receiver/dataMessage.ts +++ b/ts/receiver/dataMessage.ts @@ -232,7 +232,11 @@ export async function handleSwarmDataMessage( } if (!messageHasVisibleContent(cleanDataMessage)) { - window?.log?.debug(`WIP: Message ${getEnvelopeId(envelope)} ignored; it was empty`); + window?.log?.debug( + `WIP: Message ${getEnvelopeId(envelope)} ignored; it was empty`, + cleanDataMessage + ); + await removeFromCache(envelope); return; } @@ -257,10 +261,7 @@ export async function handleSwarmDataMessage( sentAt: sentAtTimestamp, }); - if (isSyncedMessage) { - // TODO handle sync messages expiring separately? - window.log.debug('WIP: Sync Message dropping'); - } else { + if (!isEmpty(expireUpdate)) { msgModel = handleExpireUpdate(convoToAddMessageTo, msgModel, expireUpdate); } diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts index 1fa75186e..9f87bf4f7 100644 --- a/ts/receiver/queuedJob.ts +++ b/ts/receiver/queuedJob.ts @@ -446,6 +446,7 @@ export async function handleMessageJob( providedChangeTimestamp: lastDisappearingMessageChangeTimestamp, providedSource: source, receivedAt: messageModel.get('received_at'), + // NOTE we don't commit yet because we want to get the message id, see below shouldCommit: false, existingMessage: messageModel, fromConfigMessage: false, diff --git a/ts/session/profile_manager/ProfileManager.ts b/ts/session/profile_manager/ProfileManager.ts index 705fba7f5..eea852a0b 100644 --- a/ts/session/profile_manager/ProfileManager.ts +++ b/ts/session/profile_manager/ProfileManager.ts @@ -4,15 +4,17 @@ import { UserUtils } from '../utils'; import { toHex } from '../utils/String'; import { AvatarDownload } from '../utils/job_runners/jobs/AvatarDownloadJob'; +export type Profile = { + displayName: string | undefined; + profileUrl: string | null; + profileKey: Uint8Array | null; + priority: number | null; // passing null means to not update the priority at all (used for legacy config message for now) +}; + /** * This can be used to update our conversation display name with the given name right away, and plan an AvatarDownloadJob to retrieve the new avatar if needed to download it */ -async function updateOurProfileSync( - displayName: string | undefined, - profileUrl: string | null, - profileKey: Uint8Array | null, - priority: number | null -) { +async function updateOurProfileSync({ displayName, profileUrl, profileKey, priority }: Profile) { const us = UserUtils.getOurPubKeyStrFromCache(); const ourConvo = getConversationController().get(us); if (!ourConvo?.id) { diff --git a/ts/session/utils/libsession/libsession_utils_user_profile.ts b/ts/session/utils/libsession/libsession_utils_user_profile.ts index a06b9c496..efde05fe0 100644 --- a/ts/session/utils/libsession/libsession_utils_user_profile.ts +++ b/ts/session/utils/libsession/libsession_utils_user_profile.ts @@ -25,13 +25,14 @@ async function insertUserProfileIntoWrapper(convoId: string) { const areBlindedMsgRequestEnabled = !!Storage.get(SettingsKey.hasBlindedMsgRequestsEnabled); + const expirySeconds = ourConvo.get('expireTimer') || 0; + window.log.debug( `inserting into userprofile wrapper: username:"${dbName}", priority:${priority} image:${JSON.stringify( { url: dbProfileUrl, key: dbProfileKey } - )}, settings: ${JSON.stringify({ areBlindedMsgRequestEnabled })}` + )}, settings: ${JSON.stringify({ areBlindedMsgRequestEnabled, expirySeconds })}` ); - // const expirySeconds = ourConvo.get('expireTimer') || 0; - // TODO setup getExpiry and setExpiry + if (dbProfileUrl && !isEmpty(dbProfileKey)) { await UserConfigWrapperActions.setUserInfo(dbName, priority, { url: dbProfileUrl, @@ -41,6 +42,7 @@ async function insertUserProfileIntoWrapper(convoId: string) { await UserConfigWrapperActions.setUserInfo(dbName, priority, null); } await UserConfigWrapperActions.setEnableBlindedMsgRequest(areBlindedMsgRequestEnabled); + await UserConfigWrapperActions.setNoteToSelfExpiry(expirySeconds); } function isUserProfileToStoreInWrapper(convoId: string) { diff --git a/ts/session/utils/sync/syncUtils.ts b/ts/session/utils/sync/syncUtils.ts index c188ed721..f82a23b54 100644 --- a/ts/session/utils/sync/syncUtils.ts +++ b/ts/session/utils/sync/syncUtils.ts @@ -390,7 +390,7 @@ export const buildSyncMessage = ( if (expireUpdate && !isEmpty(expireUpdate)) { return buildSyncExpireTimerMessage(identifier, expireUpdate, timestamp, syncTarget); } - window.log.warn('Building Sync Expire Timer Message failed', dataMessage, expireUpdate); + window.log.warn('WIP: Building Sync Expire Timer Message failed', dataMessage, expireUpdate); } return buildSyncVisibleMessage(identifier, dataMessage, timestamp, syncTarget); }; diff --git a/ts/util/expiringMessages.ts b/ts/util/expiringMessages.ts index f390cef2f..3d3262011 100644 --- a/ts/util/expiringMessages.ts +++ b/ts/util/expiringMessages.ts @@ -13,7 +13,9 @@ import { MessageModel } from '../models/message'; import { GetNetworkTime } from '../session/apis/snode_api/getNetworkTime'; import { ReleasedFeatures } from './releaseFeature'; -export const DisappearingMessageMode = ['deleteAfterRead', 'deleteAfterSend']; +// NOTE this must match Content.ExpirationType in the protobuf +// TODO double check this +export const DisappearingMessageMode = ['unknown', 'deleteAfterRead', 'deleteAfterSend']; export type DisappearingMessageType = typeof DisappearingMessageMode[number]; // NOTE these cannot be imported in the nodejs side yet. We need to move the types to the own file with no window imports // TODO legacy messages support will be removed in a future release @@ -289,10 +291,21 @@ export function isLegacyDisappearingModeEnabled( // TODO legacy messages support will be removed in a future release /** * This function is used to set the mode for legacy disappearing messages depending on the default for the conversation type + * + * NOTE Should only be used when sending or receiving data messages (protobuf) + * * @param convo Conversation we want to set * @returns Disappearing mode we should use */ -export function resolveLegacyDisappearingMode(convo: ConversationModel): DisappearingMessageType { +export function resolveLegacyDisappearingMode( + convo: ConversationModel, + expireTimer?: number +): DisappearingMessageType { + if (expireTimer === 0) { + // NOTE we would want this to be undefined but because of an issue with the protobuf implement we need to have a value + return 'unknown'; + } + if (convo.isMe() || convo.isClosedGroup()) { return 'deleteAfterSend'; }