mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-20 02:08:57 +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 { drop } from '../util/drop.std.js';
|
||||||
import { CallLinkFinalizeDeleteManager } from './CallLinkFinalizeDeleteManager.preload.js';
|
import { CallLinkFinalizeDeleteManager } from './CallLinkFinalizeDeleteManager.preload.js';
|
||||||
import { chatFolderCleanupService } from '../services/expiring/chatFolderCleanupService.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 { callLinkRefreshJobQueue } from './callLinkRefreshJobQueue.preload.js';
|
||||||
import { conversationJobQueue } from './conversationJobQueue.preload.js';
|
import { conversationJobQueue } from './conversationJobQueue.preload.js';
|
||||||
import { deleteDownloadsJobQueue } from './deleteDownloadsJobQueue.preload.js';
|
import { deleteDownloadsJobQueue } from './deleteDownloadsJobQueue.preload.js';
|
||||||
@@ -52,6 +53,7 @@ export function initializeAllJobQueues({
|
|||||||
drop(callLinkRefreshJobQueue.streamJobs());
|
drop(callLinkRefreshJobQueue.streamJobs());
|
||||||
drop(CallLinkFinalizeDeleteManager.start());
|
drop(CallLinkFinalizeDeleteManager.start());
|
||||||
drop(chatFolderCleanupService.start('initializeAllJobQueues'));
|
drop(chatFolderCleanupService.start('initializeAllJobQueues'));
|
||||||
|
drop(pinnedMessagesCleanupService.start('initializeAllJobQueues'));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function shutdownAllJobQueues(): Promise<void> {
|
export async function shutdownAllJobQueues(): Promise<void> {
|
||||||
@@ -67,5 +69,6 @@ export async function shutdownAllJobQueues(): Promise<void> {
|
|||||||
reportSpamJobQueue.shutdown(),
|
reportSpamJobQueue.shutdown(),
|
||||||
CallLinkFinalizeDeleteManager.stop(),
|
CallLinkFinalizeDeleteManager.stop(),
|
||||||
chatFolderCleanupService.stop('shutdownAllJobQueues'),
|
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 type { ConversationModel } from '../models/conversations.preload.js';
|
||||||
import { getPinnedMessagesLimit } from '../util/pinnedMessages.dom.js';
|
import { getPinnedMessagesLimit } from '../util/pinnedMessages.dom.js';
|
||||||
import { getPinnedMessageExpiresAt } from '../util/pinnedMessages.std.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 { AccessRequired } = Proto.AccessControl;
|
||||||
const { Role } = Proto.Member;
|
const { Role } = Proto.Member;
|
||||||
@@ -93,6 +95,8 @@ export async function onPinnedMessageAdd(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drop(pinnedMessagesCleanupService.trigger('onPinnedMessageAdd'));
|
||||||
|
|
||||||
if (result.change?.inserted) {
|
if (result.change?.inserted) {
|
||||||
await targetConversation.addNotification('pinned-message-notification', {
|
await targetConversation.addNotification('pinned-message-notification', {
|
||||||
pinnedMessageId: targetMessage.id,
|
pinnedMessageId: targetMessage.id,
|
||||||
@@ -144,6 +148,7 @@ export async function onPinnedMessageRemove(
|
|||||||
log.info(
|
log.info(
|
||||||
`Deleted pinned message ${deletedPinnedMessageId} for messageId ${targetMessageId}`
|
`Deleted pinned message ${deletedPinnedMessageId} for messageId ${targetMessageId}`
|
||||||
);
|
);
|
||||||
|
drop(pinnedMessagesCleanupService.trigger('onPinnedMessageRemove'));
|
||||||
|
|
||||||
window.reduxActions.pinnedMessages.onPinnedMessagesChanged(
|
window.reduxActions.pinnedMessages.onPinnedMessagesChanged(
|
||||||
targetConversationId
|
targetConversationId
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import { longTimeoutAsync } from '../../util/timeout.std.js';
|
|||||||
|
|
||||||
const parentLog = createLogger('ExpiringEntityCleanupService');
|
const parentLog = createLogger('ExpiringEntityCleanupService');
|
||||||
|
|
||||||
|
type EntityId = string | number;
|
||||||
|
|
||||||
export type ExpiringEntity = Readonly<{
|
export type ExpiringEntity = Readonly<{
|
||||||
id: string;
|
id: EntityId;
|
||||||
expiresAtMs: number;
|
expiresAtMs: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@@ -21,7 +23,7 @@ export type Unsubscribe = () => void;
|
|||||||
export type ExpiringEntityCleanupServiceOptions = Readonly<{
|
export type ExpiringEntityCleanupServiceOptions = Readonly<{
|
||||||
logPrefix: string;
|
logPrefix: string;
|
||||||
getNextExpiringEntity: () => Promise<ExpiringEntity | null>;
|
getNextExpiringEntity: () => Promise<ExpiringEntity | null>;
|
||||||
cleanupExpiredEntities: () => Promise<ReadonlyArray<string>>;
|
cleanupExpiredEntities: () => Promise<ReadonlyArray<EntityId>>;
|
||||||
subscribeToTriggers: (trigger: Trigger) => Unsubscribe;
|
subscribeToTriggers: (trigger: Trigger) => Unsubscribe;
|
||||||
_mockGetCurrentTime?: () => number;
|
_mockGetCurrentTime?: () => number;
|
||||||
_mockScheduleLongTimeout?: (ms: number, signal: AbortSignal) => Promise<void>;
|
_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,
|
MESSAGE_CHANGED,
|
||||||
TARGETED_CONVERSATION_CHANGED,
|
TARGETED_CONVERSATION_CHANGED,
|
||||||
} from './conversations.preload.js';
|
} from './conversations.preload.js';
|
||||||
|
import { pinnedMessagesCleanupService } from '../../services/expiring/pinnedMessagesCleanupService.preload.js';
|
||||||
|
import { drop } from '../../util/drop.std.js';
|
||||||
|
|
||||||
type PreloadData = ReadonlyDeep<{
|
type PreloadData = ReadonlyDeep<{
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
@@ -156,6 +158,7 @@ function onPinnedMessageAdd(
|
|||||||
expiresAt,
|
expiresAt,
|
||||||
pinnedAt,
|
pinnedAt,
|
||||||
});
|
});
|
||||||
|
drop(pinnedMessagesCleanupService.trigger('onPinnedMessageAdd'));
|
||||||
|
|
||||||
await targetConversation.addNotification('pinned-message-notification', {
|
await targetConversation.addNotification('pinned-message-notification', {
|
||||||
pinnedMessageId: targetMessageId,
|
pinnedMessageId: targetMessageId,
|
||||||
@@ -174,7 +177,7 @@ function onPinnedMessageRemove(targetMessageId: string): StateThunk {
|
|||||||
...target,
|
...target,
|
||||||
});
|
});
|
||||||
await DataWriter.deletePinnedMessageByMessageId(targetMessageId);
|
await DataWriter.deletePinnedMessageByMessageId(targetMessageId);
|
||||||
|
drop(pinnedMessagesCleanupService.trigger('onPinnedMessageRemove'));
|
||||||
dispatch(onPinnedMessagesChanged(target.conversationId));
|
dispatch(onPinnedMessagesChanged(target.conversationId));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ describe('createExpiringEntityCleanupService', () => {
|
|||||||
},
|
},
|
||||||
async cleanupExpiredEntities() {
|
async cleanupExpiredEntities() {
|
||||||
calls.push('cleanupExpiredEntities');
|
calls.push('cleanupExpiredEntities');
|
||||||
const deletedIds: Array<string> = [];
|
const deletedIds: Array<string | number> = [];
|
||||||
const undeleted: Array<ExpiringEntity> = [];
|
const undeleted: Array<ExpiringEntity> = [];
|
||||||
for (const entity of mockExpiringEntities) {
|
for (const entity of mockExpiringEntities) {
|
||||||
if (entity.expiresAtMs <= clock.getCurrentTime()) {
|
if (entity.expiresAtMs <= clock.getCurrentTime()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user