mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-19 17:58:48 +00:00
Schedule expiration of pinned messages
This commit is contained in:
@@ -5,6 +5,7 @@ import type { reportMessage, isOnline } from '../textsecure/WebAPI.preload.js';
|
||||
import { drop } from '../util/drop.std.js';
|
||||
import { CallLinkFinalizeDeleteManager } from './CallLinkFinalizeDeleteManager.preload.js';
|
||||
import { chatFolderCleanupService } from '../services/expiring/chatFolderCleanupService.preload.js';
|
||||
import { pinnedMessagesCleanupService } from '../services/expiring/pinnedMessagesCleanupService.preload.js';
|
||||
import { callLinkRefreshJobQueue } from './callLinkRefreshJobQueue.preload.js';
|
||||
import { conversationJobQueue } from './conversationJobQueue.preload.js';
|
||||
import { deleteDownloadsJobQueue } from './deleteDownloadsJobQueue.preload.js';
|
||||
@@ -52,6 +53,7 @@ export function initializeAllJobQueues({
|
||||
drop(callLinkRefreshJobQueue.streamJobs());
|
||||
drop(CallLinkFinalizeDeleteManager.start());
|
||||
drop(chatFolderCleanupService.start('initializeAllJobQueues'));
|
||||
drop(pinnedMessagesCleanupService.start('initializeAllJobQueues'));
|
||||
}
|
||||
|
||||
export async function shutdownAllJobQueues(): Promise<void> {
|
||||
@@ -67,5 +69,6 @@ export async function shutdownAllJobQueues(): Promise<void> {
|
||||
reportSpamJobQueue.shutdown(),
|
||||
CallLinkFinalizeDeleteManager.stop(),
|
||||
chatFolderCleanupService.stop('shutdownAllJobQueues'),
|
||||
pinnedMessagesCleanupService.stop('shutdownAllJobQueues'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import { SignalService as Proto } from '../protobuf/index.std.js';
|
||||
import type { ConversationModel } from '../models/conversations.preload.js';
|
||||
import { getPinnedMessagesLimit } from '../util/pinnedMessages.dom.js';
|
||||
import { getPinnedMessageExpiresAt } from '../util/pinnedMessages.std.js';
|
||||
import { pinnedMessagesCleanupService } from '../services/expiring/pinnedMessagesCleanupService.preload.js';
|
||||
import { drop } from '../util/drop.std.js';
|
||||
|
||||
const { AccessRequired } = Proto.AccessControl;
|
||||
const { Role } = Proto.Member;
|
||||
@@ -93,6 +95,8 @@ export async function onPinnedMessageAdd(
|
||||
}
|
||||
}
|
||||
|
||||
drop(pinnedMessagesCleanupService.trigger('onPinnedMessageAdd'));
|
||||
|
||||
if (result.change?.inserted) {
|
||||
await targetConversation.addNotification('pinned-message-notification', {
|
||||
pinnedMessageId: targetMessage.id,
|
||||
@@ -144,6 +148,7 @@ export async function onPinnedMessageRemove(
|
||||
log.info(
|
||||
`Deleted pinned message ${deletedPinnedMessageId} for messageId ${targetMessageId}`
|
||||
);
|
||||
drop(pinnedMessagesCleanupService.trigger('onPinnedMessageRemove'));
|
||||
|
||||
window.reduxActions.pinnedMessages.onPinnedMessagesChanged(
|
||||
targetConversationId
|
||||
|
||||
@@ -10,8 +10,10 @@ import { longTimeoutAsync } from '../../util/timeout.std.js';
|
||||
|
||||
const parentLog = createLogger('ExpiringEntityCleanupService');
|
||||
|
||||
type EntityId = string | number;
|
||||
|
||||
export type ExpiringEntity = Readonly<{
|
||||
id: string;
|
||||
id: EntityId;
|
||||
expiresAtMs: number;
|
||||
}>;
|
||||
|
||||
@@ -21,7 +23,7 @@ export type Unsubscribe = () => void;
|
||||
export type ExpiringEntityCleanupServiceOptions = Readonly<{
|
||||
logPrefix: string;
|
||||
getNextExpiringEntity: () => Promise<ExpiringEntity | null>;
|
||||
cleanupExpiredEntities: () => Promise<ReadonlyArray<string>>;
|
||||
cleanupExpiredEntities: () => Promise<ReadonlyArray<EntityId>>;
|
||||
subscribeToTriggers: (trigger: Trigger) => Unsubscribe;
|
||||
_mockGetCurrentTime?: () => number;
|
||||
_mockScheduleLongTimeout?: (ms: number, signal: AbortSignal) => Promise<void>;
|
||||
|
||||
33
ts/services/expiring/pinnedMessagesCleanupService.preload.ts
Normal file
33
ts/services/expiring/pinnedMessagesCleanupService.preload.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { DataReader, DataWriter } from '../../sql/Client.preload.js';
|
||||
import { createExpiringEntityCleanupService } from './createExpiringEntityCleanupService.std.js';
|
||||
import { strictAssert } from '../../util/assert.std.js';
|
||||
|
||||
export const pinnedMessagesCleanupService = createExpiringEntityCleanupService({
|
||||
logPrefix: 'PinnedMessages',
|
||||
getNextExpiringEntity: async () => {
|
||||
const nextExpiringPinnedMessage =
|
||||
await DataReader.getNextExpiringPinnedMessageAcrossConversations();
|
||||
if (nextExpiringPinnedMessage == null) {
|
||||
return null;
|
||||
}
|
||||
strictAssert(
|
||||
nextExpiringPinnedMessage.expiresAt != null,
|
||||
'nextExpiringPinnedMessage.expiresAt is null'
|
||||
);
|
||||
return {
|
||||
id: nextExpiringPinnedMessage.id,
|
||||
expiresAtMs: nextExpiringPinnedMessage.expiresAt,
|
||||
};
|
||||
},
|
||||
cleanupExpiredEntities: async () => {
|
||||
const deletedPinnedMessagesIds =
|
||||
await DataWriter.deleteAllExpiredPinnedMessagesBefore(Date.now());
|
||||
return deletedPinnedMessagesIds;
|
||||
},
|
||||
subscribeToTriggers: () => {
|
||||
return () => null;
|
||||
},
|
||||
});
|
||||
@@ -36,6 +36,8 @@ import {
|
||||
MESSAGE_CHANGED,
|
||||
TARGETED_CONVERSATION_CHANGED,
|
||||
} from './conversations.preload.js';
|
||||
import { pinnedMessagesCleanupService } from '../../services/expiring/pinnedMessagesCleanupService.preload.js';
|
||||
import { drop } from '../../util/drop.std.js';
|
||||
|
||||
type PreloadData = ReadonlyDeep<{
|
||||
conversationId: string;
|
||||
@@ -156,6 +158,7 @@ function onPinnedMessageAdd(
|
||||
expiresAt,
|
||||
pinnedAt,
|
||||
});
|
||||
drop(pinnedMessagesCleanupService.trigger('onPinnedMessageAdd'));
|
||||
|
||||
await targetConversation.addNotification('pinned-message-notification', {
|
||||
pinnedMessageId: targetMessageId,
|
||||
@@ -174,7 +177,7 @@ function onPinnedMessageRemove(targetMessageId: string): StateThunk {
|
||||
...target,
|
||||
});
|
||||
await DataWriter.deletePinnedMessageByMessageId(targetMessageId);
|
||||
|
||||
drop(pinnedMessagesCleanupService.trigger('onPinnedMessageRemove'));
|
||||
dispatch(onPinnedMessagesChanged(target.conversationId));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ describe('createExpiringEntityCleanupService', () => {
|
||||
},
|
||||
async cleanupExpiredEntities() {
|
||||
calls.push('cleanupExpiredEntities');
|
||||
const deletedIds: Array<string> = [];
|
||||
const deletedIds: Array<string | number> = [];
|
||||
const undeleted: Array<ExpiringEntity> = [];
|
||||
for (const entity of mockExpiringEntities) {
|
||||
if (entity.expiresAtMs <= clock.getCurrentTime()) {
|
||||
|
||||
Reference in New Issue
Block a user