From 897c051c99a3cd264293e7be65d7843cb42912c4 Mon Sep 17 00:00:00 2001 From: Jamie <113370520+jamiebuilds-signal@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:12:10 -0800 Subject: [PATCH] Add expiration timer to pinned message notification --- ts/background.preload.ts | 2 ++ ts/messageModifiers/PinnedMessages.preload.ts | 7 +++-- ts/models/conversations.preload.ts | 31 +++++++++++++++++++ ts/sql/Server.node.ts | 17 ++++------ ts/state/ducks/conversations.preload.ts | 6 ++-- ts/types/Message2.preload.ts | 15 +-------- 6 files changed, 49 insertions(+), 29 deletions(-) diff --git a/ts/background.preload.ts b/ts/background.preload.ts index dc0a10a1e1..cde3da358a 100644 --- a/ts/background.preload.ts +++ b/ts/background.preload.ts @@ -2493,6 +2493,7 @@ export async function startApp(): Promise { targetAuthorAci: data.message.pinMessage.targetAuthorAci, pinDuration: data.message.pinMessage.pinDuration, pinnedByAci: data.sourceAci, + sentAtTimestamp: data.timestamp, receivedAtTimestamp: data.receivedAtDate, }); confirm(); @@ -3000,6 +3001,7 @@ export async function startApp(): Promise { targetAuthorAci: data.message.pinMessage.targetAuthorAci, pinDuration: data.message.pinMessage.pinDuration, pinnedByAci: sourceServiceId, + sentAtTimestamp: data.timestamp, receivedAtTimestamp: data.receivedAtDate, }); confirm(); diff --git a/ts/messageModifiers/PinnedMessages.preload.ts b/ts/messageModifiers/PinnedMessages.preload.ts index 0315b1d920..c7a583b9c5 100644 --- a/ts/messageModifiers/PinnedMessages.preload.ts +++ b/ts/messageModifiers/PinnedMessages.preload.ts @@ -27,6 +27,7 @@ export type PinnedMessageAddProps = Readonly<{ targetAuthorAci: AciString; pinDuration: DurationInSeconds | null; pinnedByAci: AciString; + sentAtTimestamp: number; receivedAtTimestamp: number; }>; @@ -115,12 +116,14 @@ export async function onPinnedMessageAdd( drop(pinnedMessagesCleanupService.trigger('onPinnedMessageAdd')); if (result.change?.inserted) { - await targetConversation.addNotification('pinned-message-notification', { + await targetConversation.addPinnedMessageNotification({ pinMessage: { targetSentTimestamp: props.targetSentTimestamp, targetAuthorAci: props.targetAuthorAci, }, - sourceServiceId: props.pinnedByAci, + senderAci: props.pinnedByAci, + sentAtTimestamp: props.sentAtTimestamp, + receivedAtTimestamp: props.receivedAtTimestamp, }); } diff --git a/ts/models/conversations.preload.ts b/ts/models/conversations.preload.ts index 4cab3357f4..3876a624fc 100644 --- a/ts/models/conversations.preload.ts +++ b/ts/models/conversations.preload.ts @@ -12,6 +12,7 @@ import type { ConversationLastProfileType, ConversationRenderInfoType, MessageAttributesType, + PinMessageData, QuotedMessageType, SenderKeyInfoType, SettableConversationAttributesType, @@ -3561,6 +3562,36 @@ export class ConversationModel { await maybeNotify({ message: message.attributes, conversation: this }); } + async addPinnedMessageNotification(params: { + pinMessage: PinMessageData; + senderAci: AciString; + sentAtTimestamp: number; + receivedAtTimestamp: number; + }): Promise { + const ourAci = itemStorage.user.getCheckedAci(); + const senderIsMe = params.senderAci === ourAci; + + const message = new MessageModel({ + ...generateMessageId(incrementMessageCounter()), + conversationId: this.id, + type: 'pinned-message-notification', + sent_at: params.sentAtTimestamp, + received_at_ms: params.receivedAtTimestamp, + timestamp: params.sentAtTimestamp, + readStatus: senderIsMe ? ReadStatus.Read : ReadStatus.Unread, + seenStatus: senderIsMe ? SeenStatus.Seen : SeenStatus.Unseen, + sourceServiceId: params.senderAci, + expireTimer: this.get('expireTimer'), + expirationStartTimestamp: senderIsMe ? params.sentAtTimestamp : null, + pinMessage: params.pinMessage, + }); + + await window.MessageCache.saveMessage(message, { forceSave: true }); + window.MessageCache.register(message); + + drop(this.onNewMessage(message)); + } + async addNotification( type: MessageAttributesType['type'], extra: Partial = {} diff --git a/ts/sql/Server.node.ts b/ts/sql/Server.node.ts index 52897bd6c6..83545f841b 100644 --- a/ts/sql/Server.node.ts +++ b/ts/sql/Server.node.ts @@ -3462,7 +3462,7 @@ function getUnreadByConversationAndMarkRead( WHERE conversationId = ${conversationId} AND ${storyReplyFilter} AND - type IN ('incoming', 'poll-terminate') AND + type IS NOT 'outgoing' AND hasExpireTimer IS 1 AND received_at <= ${readMessageReceivedAt} `; @@ -5901,16 +5901,11 @@ function getMessagesUnexpectedlyMissingExpirationStartTimestamp( INDEXED BY messages_unexpectedly_missing_expiration_start_timestamp WHERE expireTimer > 0 AND - expirationStartTimestamp IS NULL AND - ( - type IS 'outgoing' OR - (type IS 'incoming' AND ( - readStatus = ${ReadStatus.Read} OR - readStatus = ${ReadStatus.Viewed} OR - readStatus IS NULL - )) OR - (type IS 'poll-terminate') - ); + expirationStartTimestamp IS NULL AND ( + readStatus = ${ReadStatus.Read} OR + readStatus = ${ReadStatus.Viewed} OR + readStatus IS NULL + ) ` ) .all(); diff --git a/ts/state/ducks/conversations.preload.ts b/ts/state/ducks/conversations.preload.ts index 12b8827fa0..ac9c006f20 100644 --- a/ts/state/ducks/conversations.preload.ts +++ b/ts/state/ducks/conversations.preload.ts @@ -5218,12 +5218,14 @@ function onPinnedMessageAdd( }); drop(pinnedMessagesCleanupService.trigger('onPinnedMessageAdd')); - await targetConversation.addNotification('pinned-message-notification', { + await targetConversation.addPinnedMessageNotification({ pinMessage: { targetSentTimestamp: target.targetSentTimestamp, targetAuthorAci: target.targetAuthorAci, }, - sourceServiceId: itemStorage.user.getCheckedAci(), + senderAci: itemStorage.user.getCheckedAci(), + sentAtTimestamp: pinnedAt, + receivedAtTimestamp: pinnedAt, }); dispatch(onPinnedMessagesChanged(target.conversationId)); diff --git a/ts/types/Message2.preload.ts b/ts/types/Message2.preload.ts index 41180b8622..46e99fb633 100644 --- a/ts/types/Message2.preload.ts +++ b/ts/types/Message2.preload.ts @@ -251,7 +251,7 @@ export const _withSchemaVersion = ({ upgradedMessage = await upgrade(message, context); } catch (error) { logger.error( - `Message._withSchemaVersion: error updating message ${message.id}, + `Message._withSchemaVersion: error updating message ${message.id}, attempt ${message.schemaMigrationAttempts}:`, Errors.toLogFormat(error) ); @@ -1121,20 +1121,7 @@ export async function migrateBodyAttachmentToDisk( export const isUserMessage = (message: MessageAttributesType): boolean => message.type === 'incoming' || message.type === 'outgoing'; -// NB: if adding more expiring message types, be sure to also update -// getUnreadByConversationAndMarkRead & -// getMessagesUnexpectedlyMissingExpirationStartTimestamp -export const EXPIRING_MESSAGE_TYPES = new Set([ - 'incoming', - 'outgoing', - 'poll-terminate', -]); - export const isExpiringMessage = (message: MessageAttributesType): boolean => { - if (!EXPIRING_MESSAGE_TYPES.has(message.type)) { - return false; - } - const { expireTimer } = message; return typeof expireTimer === 'number' && expireTimer > 0;