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) {