diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index d0f52e18b..40a0a011b 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -321,7 +321,7 @@ .module-conversation-list-item__header__name { flex-grow: 1; - flex-shrink: 1; + flex-shrink: 1000; // we need this to take all the shrinking instead of the ListItemIcons font-size: 14px; line-height: 18px; diff --git a/ts/components/dialog/UpdateConversationDetailsDialog.tsx b/ts/components/dialog/UpdateConversationDetailsDialog.tsx index e7c040625..5e854d8d7 100644 --- a/ts/components/dialog/UpdateConversationDetailsDialog.tsx +++ b/ts/components/dialog/UpdateConversationDetailsDialog.tsx @@ -101,7 +101,12 @@ function useDescriptionErrorString({ // description is always optional return ''; } - const charLength = newDescription?.length || 0; + + // "👨🏻‍❤️‍💋‍👨🏻" is supposed to be 1char, but 35 bytes, and this is the only way + // I found to have this to have the correct count + const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' }); + const charLength = [...segmenter.segment(newDescription)].length; + const byteLength = new TextEncoder().encode(newDescription).length; if ( diff --git a/ts/components/dialog/user-settings/components.tsx b/ts/components/dialog/user-settings/components.tsx index e694bae6b..570f135ad 100644 --- a/ts/components/dialog/user-settings/components.tsx +++ b/ts/components/dialog/user-settings/components.tsx @@ -91,7 +91,7 @@ const StyledProfileName = styled(Flex)` } `; -const StyledName = styled.p` +const StyledName = styled.div` font-size: var(--font-size-xl); line-height: 1.4; font-weight: 700; @@ -116,14 +116,15 @@ export const ProfileName = (props: { profileName: string; onClick: () => void }) {profileName} + {showPro.show ? ( + + ) : null} - {showPro.show ? ( - - ) : null} ); }; diff --git a/ts/components/leftpane/conversation-list-item/HeaderItem.tsx b/ts/components/leftpane/conversation-list-item/HeaderItem.tsx index e2e70d256..5f5eb3ed0 100644 --- a/ts/components/leftpane/conversation-list-item/HeaderItem.tsx +++ b/ts/components/leftpane/conversation-list-item/HeaderItem.tsx @@ -36,6 +36,7 @@ const NotificationSettingIcon = () => { iconType="mute" iconColor={'var(--conversation-tab-text-color)'} iconSize="small" + style={{ flexShrink: 0 }} /> ); case 'mentions_only': @@ -44,6 +45,7 @@ const NotificationSettingIcon = () => { iconType="bell" iconColor={'var(--conversation-tab-text-color)'} iconSize="small" + style={{ flexShrink: 0 }} /> ); default: @@ -63,7 +65,12 @@ const PinIcon = () => { const isPinned = useIsPinned(conversationId); return isPinned ? ( - + ) : null; }; @@ -100,6 +107,7 @@ const MentionAtSymbol = styled.span` min-width: 16px; border-radius: 8px; cursor: pointer; + flex-shrink: 0; &:hover { filter: grayscale(0.7); diff --git a/ts/components/leftpane/overlay/choose-action/ContactRow.tsx b/ts/components/leftpane/overlay/choose-action/ContactRow.tsx index e0066d286..bf42db0c1 100644 --- a/ts/components/leftpane/overlay/choose-action/ContactRow.tsx +++ b/ts/components/leftpane/overlay/choose-action/ContactRow.tsx @@ -1,8 +1,18 @@ +import { createPortal } from 'react-dom'; +import { contextMenu } from 'react-contexify'; +import type { ReactNode } from 'react'; import styled, { CSSProperties } from 'styled-components'; + import { openConversationWithMessages } from '../../../../state/ducks/conversations'; import { Avatar, AvatarSize } from '../../../avatar/Avatar'; import { useShowUserDetailsCbFromConversation } from '../../../menuAndSettingsHooks/useShowUserDetailsCb'; import { ContactName } from '../../../conversation/ContactName/ContactName'; +import { MemoConversationListItemContextMenu } from '../../../menu/ConversationListItemContextMenu'; +import { ContextConversationProvider } from '../../../../contexts/ConvoIdContext'; + +const Portal = ({ children }: { children: ReactNode }) => { + return createPortal(children, document.querySelector('.inbox.index') as Element); +}; type Props = { id: string; displayName?: string; style: CSSProperties }; @@ -56,23 +66,35 @@ export const ContactRowBreak = (props: { char: string; key: string; style: CSSPr export const ContactRow = (props: Props) => { const { id, style } = props; + const triggerId = `contact-row-${id}-ctxmenu`; return ( - openConversationWithMessages({ conversationKey: id, messageId: null })} - > - - + openConversationWithMessages({ conversationKey: id, messageId: null })} + onContextMenu={e => { + contextMenu.show({ + id: triggerId, + event: e, + }); }} - /> - + > + + + + + + + ); }; diff --git a/ts/mains/main_node.ts b/ts/mains/main_node.ts index df0ee1ab9..c45e85ae7 100644 --- a/ts/mains/main_node.ts +++ b/ts/mains/main_node.ts @@ -709,6 +709,10 @@ app.on('ready', async () => { const userDataPath = getRealPath(app.getPath('userData')); const installPath = getRealPath(join(app.getAppPath(), '..', '..')); + // Register signal handlers + process.on('SIGINT', () => gracefulShutdown('SIGINT')); // Ctrl+C + process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); // Termination request + installFileHandler({ protocol: electronProtocol, userDataPath, @@ -859,7 +863,7 @@ async function requestShutdown() { setTimeout(() => { console.log('requestShutdown: Response never received; forcing shutdown.'); resolve(undefined); - }, 2 * DURATION.MINUTES); + }, 1 * DURATION.MINUTES); }); try { @@ -869,6 +873,13 @@ async function requestShutdown() { } } +async function gracefulShutdown(signal: string) { + console.warn(`Received ${signal}, shutting down...`); + await requestShutdown(); + app.quit(); + console.warn(`Received ${signal}, shutting down: done`); +} + app.on('before-quit', () => { console.log('before-quit event', { readyForShutdown: mainWindow ? readyForShutdown : null, diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 614051b79..2025239e6 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -275,14 +275,14 @@ export class ConversationModel extends Model { const type = this.get('type'); switch (type) { case ConversationTypeEnum.PRIVATE: - return this.id; + return `priv:${ed25519Str(this.id)}`; case ConversationTypeEnum.GROUPV2: - return `group(${ed25519Str(this.id)})`; + return `group:${ed25519Str(this.id)}`; case ConversationTypeEnum.GROUP: { if (this.isPublic()) { - return this.id; + return `comm:${this.id}`; } - return `group(${ed25519Str(this.id)})`; + return `group_legacy:${ed25519Str(this.id)}`; } default: assertUnreachable(type, `idForLogging case not handled for type:"${type}"`); @@ -954,10 +954,7 @@ export class ConversationModel extends Model { const networkTimestamp = NetworkTime.now(); window?.log?.info( - 'Sending message to conversation', - this.idForLogging(), - 'with networkTimestamp: ', - networkTimestamp + `Sending message to conversation ${this.idForLogging()} with networkTimestamp: ${networkTimestamp}` ); const attachmentsWithVoiceMessage = attachments @@ -1163,13 +1160,12 @@ export class ConversationModel extends Model { } } - // Note: we agreed that a **legacy closed** group ControlMessage message does not expire. - // Group v2 on the other hand, have expiring disappearing control message + // Private chats and group v2 are the two types of conversation + // where this function can be called, and both have expiring + // disappearing control messages. - message.setExpirationType( - this.isClosedGroup() && !this.isClosedGroupV2() ? 'unknown' : expirationType - ); - message.setExpireTimer(this.isClosedGroup() && !this.isClosedGroupV2() ? 0 : expireTimer); + message.setExpirationType(expirationType); + message.setExpireTimer(expireTimer); if (!message.id) { message.setId(v4()); diff --git a/ts/models/message.ts b/ts/models/message.ts index 40fec8100..5837dc7e7 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -8,6 +8,7 @@ import { ContentMessage } from '../session/messages/outgoing'; import { ClosedGroupV2VisibleMessage } from '../session/messages/outgoing/visibleMessage/ClosedGroupVisibleMessage'; import { PubKey } from '../session/types'; import { + MessageUtils, UserUtils, attachmentIdAsStrFromUrl, uploadAttachmentsToFileServer, @@ -89,7 +90,7 @@ import { getPromotedGroupUpdateChangeStr, } from './groupUpdate'; import { NetworkTime } from '../util/NetworkTime'; -import { MessageQueue } from '../session/sending'; +import { MessageQueue, MessageSender } from '../session/sending'; import { getTimerNotificationStr } from './timerNotifications'; import { ExpirationTimerUpdate, @@ -173,7 +174,7 @@ export class MessageModel extends Model { } public idForLogging() { - return `${this.get('source')} ${this.get('sent_at')}`; + return `msg(${this.id}, ${this.getConversation()?.idForLogging()}, ${this.get('sent_at')})`; } public isExpirationTimerUpdate() { @@ -1113,9 +1114,14 @@ export class MessageModel extends Model { ); if (syncMessage) { - await MessageQueue.use().sendSyncMessage({ - namespace: SnodeNamespaces.Default, - message: syncMessage, + await MessageSender.sendSingleMessage({ + isSyncMessage: true, + message: await MessageUtils.toRawMessage( + PubKey.cast(UserUtils.getOurPubKeyStrFromCache()), + syncMessage, + SnodeNamespaces.Default + ), + abortSignal: null, }); } } @@ -1359,6 +1365,13 @@ export class MessageModel extends Model { this.set({ expirationStartTimestamp }); } + public setExpiresAt(expiresAt: number | undefined) { + if (expiresAt === this.getExpiresAt()) { + return; + } + this.set({ expires_at: expiresAt }); + } + public getPreview() { return this.get('preview'); } diff --git a/ts/models/messageFactory.ts b/ts/models/messageFactory.ts index c56a3d63e..23e433700 100644 --- a/ts/models/messageFactory.ts +++ b/ts/models/messageFactory.ts @@ -83,6 +83,7 @@ function getSharedAttributesForPublicMessage({ messageHash: '', // we do not care of a messageHash for an opengroup message. we have serverId for that // NOTE Community messages do not support disappearing messages expirationStartTimestamp: undefined, + expires_at: undefined, }; } diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts index 49898c797..cc96ecad6 100644 --- a/ts/receiver/queuedJob.ts +++ b/ts/receiver/queuedJob.ts @@ -365,7 +365,7 @@ async function markConvoAsReadIfOutgoingMessage( const isOutgoingMessage = message.get('type') === 'outgoing' || message.get('direction') === 'outgoing'; if (isOutgoingMessage) { - const sentAt = message.get('sent_at') || message.get('serverTimestamp'); + const sentAt = message.get('serverTimestamp') || message.get('sent_at'); if (sentAt) { const expirationType = message.getExpirationType(); const expireTimer = message.getExpireTimerSeconds(); @@ -432,16 +432,19 @@ export async function handleMessageJob( messageModel.getExpirationType(), messageModel.getExpireTimerSeconds() ); - - if (expirationMode === 'deleteAfterSend') { - messageModel.setMessageExpirationStartTimestamp( - DisappearingMessages.setExpirationStartTimestamp( - expirationMode, - messageModel.get('sent_at'), - 'handleMessageJob', - messageModel.id - ) + const expireTimer = messageModel.getExpireTimerSeconds(); + + if (expirationMode === 'deleteAfterSend' && expireTimer > 0) { + const expirationStartTimestamp = DisappearingMessages.setExpirationStartTimestamp( + expirationMode, + messageModel.get('sent_at'), + 'handleMessageJob', + messageModel.id ); + if (expirationStartTimestamp) { + messageModel.setMessageExpirationStartTimestamp(expirationStartTimestamp); + messageModel.setExpiresAt(expirationStartTimestamp + expireTimer * 1000); + } } } diff --git a/ts/session/disappearing_messages/index.ts b/ts/session/disappearing_messages/index.ts index af00bd577..f9b1960ae 100644 --- a/ts/session/disappearing_messages/index.ts +++ b/ts/session/disappearing_messages/index.ts @@ -170,7 +170,7 @@ function setExpirationStartTimestamp( // these are for debugging purposes callLocation?: string, messageId?: string -): number | undefined { +) { let expirationStartTimestamp: number | undefined = NetworkTime.now(); if (callLocation) { @@ -184,7 +184,7 @@ function setExpirationStartTimestamp( if (timestamp) { if (!isValidUnixTimestamp(timestamp)) { window.log.debug( - `[setExpirationStartTimestamp] We compared 2 timestamps for a disappearing message (${mode}) and the argument timestamp is not a invalid unix timestamp.${ + `[setExpirationStartTimestamp] We compared 2 timestamps for a disappearing message (${mode}) and the argument timestamp is an invalid unix timestamp.${ messageId ? `messageId: ${messageId} ` : '' }` ); @@ -361,7 +361,15 @@ async function checkForExpireUpdateInContentMessage( /** * Checks if an outgoing message is meant to disappear and if so trigger the timer */ -function checkForExpiringOutgoingMessage(message: MessageModel, location?: string) { +function checkForExpiringOutgoingMessage({ + effectivelyStoredAtMs, + location, + message, +}: { + message: MessageModel; + location: string; + effectivelyStoredAtMs: number; +}) { const convo = message.getConversation(); const expireTimer = message.getExpireTimerSeconds(); const expirationType = message.getExpirationType(); @@ -379,13 +387,17 @@ function checkForExpiringOutgoingMessage(message: MessageModel, location?: strin const expirationMode = changeToDisappearingConversationMode(convo, expirationType, expireTimer); if (expirationMode !== 'off') { - message.set({ - expirationStartTimestamp: setExpirationStartTimestamp( - expirationMode, - message.get('sent_at'), - location - ), - }); + const expirationStartTimestamp = setExpirationStartTimestamp( + expirationMode, + effectivelyStoredAtMs, + location + ); + if (expirationStartTimestamp) { + message.set({ + expirationStartTimestamp, + expires_at: expirationStartTimestamp + expireTimer * 1000, + }); + } } } } @@ -499,14 +511,17 @@ function getMessageReadyToDisappear( messageExpirationFromRetrieve > 0 ) { // Note: closed groups control message do not disappear - if (!conversationModel.isClosedGroup() && !messageModel.isControlMessage()) { - const expirationStartTimestamp = messageExpirationFromRetrieve - expireTimer * 1000; - const expires_at = messageExpirationFromRetrieve; - messageModel.set({ - expirationStartTimestamp, - expires_at, - }); - } + + const expirationStartTimestamp = messageExpirationFromRetrieve - expireTimer * 1000; + const expires_at = messageExpirationFromRetrieve; + + window.log.debug( + `incoming DaS message,\n\tforcing expirationStartTimestamp to ${expirationStartTimestamp} which is ${(Date.now() - expirationStartTimestamp) / 1000}s ago,\n\tand expires_at to ${expires_at} so with ${(messageExpirationFromRetrieve - Date.now()) / 1000}s left` + ); + messageModel.set({ + expirationStartTimestamp, + expires_at, + }); } return messageModel; diff --git a/ts/session/sending/MessageSender.ts b/ts/session/sending/MessageSender.ts index 57802b220..69b07bdac 100644 --- a/ts/session/sending/MessageSender.ts +++ b/ts/session/sending/MessageSender.ts @@ -673,7 +673,7 @@ async function handleBatchResultWithSubRequests({ // there are some things we need to do when storing messages // for groups/legacy groups or user (but not for config messages) if (isStoreUserInitiatedSubRequest(subRequest)) { - const storedAt = batchResult?.[index]?.body?.t; + const storedAtMs = batchResult?.[index]?.body?.t; const storedHash = batchResult?.[index]?.body?.hash; const subRequestStatusCode = batchResult?.[index]?.code; // TODO: the expiration is due to be returned by the storage server on "store" soon, we will then be able to use it instead of doing the storedAt + ttl logic below @@ -682,7 +682,7 @@ async function handleBatchResultWithSubRequests({ subRequestStatusCode === 200 && !isEmpty(storedHash) && isString(storedHash) && - isNumber(storedAt) + isNumber(storedAtMs) ) { seenHashes.push({ expiresAt: NetworkTime.now() + TTL_DEFAULT.CONTENT_MESSAGE, // non config msg expire at CONTENT_MESSAGE at most @@ -694,19 +694,18 @@ async function handleBatchResultWithSubRequests({ // For groups, we can just store that hash directly as the group's swarm is hosting all of the group messages if (subRequest.dbMessageIdentifier) { // eslint-disable-next-line no-await-in-loop - await MessageSentHandler.handleSwarmMessageSentSuccess( - { - device: subRequest.destination, - isDestinationClosedGroup: MessageSender.destinationIsClosedGroup(destination), - identifier: subRequest.dbMessageIdentifier, - plainTextBuffer: - subRequest instanceof StoreUserMessageSubRequest - ? subRequest.plainTextBuffer - : null, - }, - storedAt, - storedHash - ); + await MessageSentHandler.handleSwarmMessageSentSuccess({ + device: subRequest.destination, + isDestinationClosedGroup: MessageSender.destinationIsClosedGroup(destination), + identifier: subRequest.dbMessageIdentifier, + plainTextBuffer: + subRequest instanceof StoreUserMessageSubRequest ? subRequest.plainTextBuffer : null, + // Note: we cannot override this effective timestamp, as this is the one used as an id for + // quotes and read receipts. + sentAtMs: subRequest.createdAtNetworkTimestamp, + storedAtServerMs: storedAtMs, + storedHash, + }); } } } diff --git a/ts/session/sending/MessageSentHandler.ts b/ts/session/sending/MessageSentHandler.ts index 6f376fcb5..b809ffb1a 100644 --- a/ts/session/sending/MessageSentHandler.ts +++ b/ts/session/sending/MessageSentHandler.ts @@ -59,26 +59,39 @@ async function handlePublicMessageSentFailure(sentMessage: OpenGroupVisibleMessa await fetchedMessage.getConversation()?.updateLastMessage(); } -async function handleSwarmMessageSentSuccess( - { - device: destination, - identifier, - isDestinationClosedGroup, - plainTextBuffer, - }: Pick & { - /** - * plainTextBuffer is only required when sending a message to a 1o1, - * as we need it to encrypt it again for our linked devices (synced messages) - */ - plainTextBuffer: Uint8Array | null; - /** - * We must not sync a message when it was sent to a closed group - */ - isDestinationClosedGroup: boolean; - }, - effectiveTimestamp: number, - storedHash: string | null -) { +async function handleSwarmMessageSentSuccess({ + device: destination, + identifier, + isDestinationClosedGroup, + plainTextBuffer, + sentAtMs, + storedAtServerMs, + storedHash, +}: Pick & { + /** + * plainTextBuffer is only required when sending a message to a 1o1, + * as we need it to encrypt it again for our linked devices (synced messages) + */ + plainTextBuffer: Uint8Array | null; + /** + * We must not sync a message when it was sent to a closed group + */ + isDestinationClosedGroup: boolean; + /** + * The timestamp when the message was reported as stored at by the server. + * This is the one we should use to start the disappearing message timer. + */ + storedAtServerMs: number; + /** + * The timestamp when the message was built locally, i.e. how it will be ordered. + * This timestamp is also used for read receipts identification and quotes. + */ + sentAtMs: number; + /** + * The hash of the message as reported by the server. + */ + storedHash: string | null; +}) { // The wrappedEnvelope will be set only if the message is not one of OpenGroupV2Message type. let fetchedMessage = await fetchHandleMessageSentData(identifier); if (!fetchedMessage) { @@ -102,8 +115,8 @@ async function handleSwarmMessageSentSuccess( // A message is synced if we triggered a sync message (sentSync) // and the current message was sent to our device (so a sync message) - const shouldMarkMessageAsSynced = - (isOurDevice && fetchedMessage.get('sentSync')) || isClosedGroupMessage; + const isPrivateSyncMessage = isOurDevice && fetchedMessage.get('sentSync'); + const shouldMarkMessageAsSynced = isPrivateSyncMessage || isClosedGroupMessage; // Handle the sync logic here if (shouldTriggerSyncMessage && plainTextBuffer) { @@ -111,7 +124,7 @@ async function handleSwarmMessageSentSuccess( const contentDecoded = SignalService.Content.decode(plainTextBuffer); if (contentDecoded && contentDecoded.dataMessage) { try { - await fetchedMessage.sendSyncMessage(contentDecoded, effectiveTimestamp); + await fetchedMessage.sendSyncMessage(contentDecoded, sentAtMs); const tempFetchMessage = await fetchHandleMessageSentData(identifier); if (!tempFetchMessage) { window?.log?.warn( @@ -141,11 +154,17 @@ async function handleSwarmMessageSentSuccess( fetchedMessage.set({ sent_to: sentTo, sent: true, - sent_at: effectiveTimestamp, + sent_at: sentAtMs, errors: undefined, }); - DisappearingMessages.checkForExpiringOutgoingMessage(fetchedMessage, 'handleMessageSentSuccess'); + if (!isPrivateSyncMessage) { + DisappearingMessages.checkForExpiringOutgoingMessage({ + message: fetchedMessage, + location: 'handleSwarmMessageSentSuccess', + effectivelyStoredAtMs: storedAtServerMs, + }); + } await fetchedMessage.commit(); fetchedMessage.getConversation()?.updateLastMessage(); @@ -182,6 +201,7 @@ async function handleSwarmMessageSentFailure( if (fetchedMessage.getExpirationType() && fetchedMessage.getExpireTimerSeconds() > 0) { fetchedMessage.set({ expirationStartTimestamp: undefined, + expires_at: undefined, }); window.log.warn( `[handleSwarmMessageSentFailure] Stopping a message from disappearing until we retry the send operation. messageId: ${fetchedMessage.get( diff --git a/ts/session/utils/Attachments.ts b/ts/session/utils/Attachments.ts index 0eb91c195..4f4aa2f6d 100644 --- a/ts/session/utils/Attachments.ts +++ b/ts/session/utils/Attachments.ts @@ -108,7 +108,7 @@ export async function uploadLinkPreviewToFileServer( // some links do not have an image associated, and it makes the whole message fail to send if (!preview?.image) { if (!preview) { - window.log.warn('tried to upload file to FileServer without image.. skipping'); + window.log.debug('tried to upload file to FileServer without image.. skipping'); } return preview as any; } diff --git a/ts/session/utils/job_runners/jobs/FetchMsgExpirySwarmJob.ts b/ts/session/utils/job_runners/jobs/FetchMsgExpirySwarmJob.ts index e6fa7aece..5df026c98 100644 --- a/ts/session/utils/job_runners/jobs/FetchMsgExpirySwarmJob.ts +++ b/ts/session/utils/job_runners/jobs/FetchMsgExpirySwarmJob.ts @@ -81,15 +81,15 @@ class FetchMsgExpirySwarmJob extends PersistedJob { }); describe('checkForExpiringInOutgoingMessage', () => { - it('if the message is supposed to disappear then the expirationStartTimestamp should be set to the sent_at value', async () => { + it('if the message is supposed to disappear then the expirationStartTimestamp should be set to the stored_At value', async () => { const conversation = new ConversationModel({ ...conversationArgs, id: ourNumber, @@ -399,17 +399,22 @@ describe('DisappearingMessage', () => { message.setSentAt(NetworkTime.now()); message.setExpireTimer(300); message.setExpirationType('deleteAfterRead'); + const storedAt = NetworkTime.now(); Sinon.stub(message, 'getConversation').returns(conversation); - DisappearingMessages.checkForExpiringOutgoingMessage(message, 'unit tests'); + DisappearingMessages.checkForExpiringOutgoingMessage({ + message, + location: 'unit tests', + effectivelyStoredAtMs: storedAt, + }); expect(message.getExpirationStartTimestamp(), 'it should be defined').to.not.be.undefined; expect( isValidUnixTimestamp(message.getExpirationStartTimestamp()), 'it should be a valid unix timestamp' ).to.be.true; - expect(message.getExpirationStartTimestamp(), 'it should equal the sent_at value').to.equal( - message.get('sent_at') + expect(message.getExpirationStartTimestamp(), 'it should equal the stored_at value').to.equal( + storedAt ); }); it('if there is no expireTimer then the expirationStartTimestamp should be undefined', async () => { @@ -422,8 +427,13 @@ describe('DisappearingMessage', () => { message.setSentAt(NetworkTime.now()); message.setExpirationType('deleteAfterRead'); Sinon.stub(message, 'getConversation').returns(conversation); + const storedAt = NetworkTime.now(); - DisappearingMessages.checkForExpiringOutgoingMessage(message, 'unit tests'); + DisappearingMessages.checkForExpiringOutgoingMessage({ + message, + location: 'unit tests', + effectivelyStoredAtMs: storedAt, + }); expect(message.getExpirationStartTimestamp(), 'it should be undefined').to.be.undefined; }); @@ -433,11 +443,17 @@ describe('DisappearingMessage', () => { id: ourNumber, }); const message = generateFakeOutgoingPrivateMessage(conversation.id); + const storedAt = NetworkTime.now(); + message.setSentAt(NetworkTime.now()); message.setExpireTimer(300); Sinon.stub(message, 'getConversation').returns(conversation); - DisappearingMessages.checkForExpiringOutgoingMessage(message, 'unit tests'); + DisappearingMessages.checkForExpiringOutgoingMessage({ + message, + location: 'unit tests', + effectivelyStoredAtMs: storedAt, + }); expect(message.getExpirationStartTimestamp(), 'it should be undefined').to.be.undefined; }); @@ -455,8 +471,13 @@ describe('DisappearingMessage', () => { message.setMessageExpirationStartTimestamp(now + 10000); Sinon.stub(message, 'getConversation').returns(conversation); + const storedAt = NetworkTime.now(); - DisappearingMessages.checkForExpiringOutgoingMessage(message, 'unit tests'); + DisappearingMessages.checkForExpiringOutgoingMessage({ + message, + location: 'unit tests', + effectivelyStoredAtMs: storedAt, + }); expect(message.getExpirationStartTimestamp(), 'it should be defined').to.not.be.undefined; diff --git a/ts/util/readReceipts.ts b/ts/util/readReceipts.ts index 2b0e89ece..6c2a1cab8 100644 --- a/ts/util/readReceipts.ts +++ b/ts/util/readReceipts.ts @@ -19,7 +19,6 @@ async function getTargetMessage(reader: string, messages: Array) { async function onReadReceipt(receipt: { source: string; timestamp: number; readAt: number }) { try { const messages = await Data.getMessagesBySentAt(receipt.timestamp); - const message = await getTargetMessage(receipt.source, messages); if (!message) {