import { Room } from 'matrix-js-sdk'; import { StateEvent } from '../../types/matrix/room'; // MSC1763 — per-room message retention (`m.room.retention`). `max_lifetime` is a // duration in milliseconds after which a message is considered expired. export type RetentionContent = { max_lifetime?: number; }; const DAY_MS = 24 * 60 * 60 * 1000; // Floor to avoid foot-guns (an admin fat-fingering a tiny value nuking a room). export const RETENTION_MIN_MS = 10 * 60 * 1000; export type RetentionPreset = { label: string; ms: number }; export const RETENTION_PRESETS: RetentionPreset[] = [ { label: 'Off', ms: 0 }, { label: '1 Day', ms: DAY_MS }, { label: '1 Week', ms: 7 * DAY_MS }, { label: '1 Month', ms: 30 * DAY_MS }, ]; /** * [Gitea #298] A usable retention window, or `undefined`. Anything under * RETENTION_MIN_MS is treated as off: a fat-fingered `max_lifetime: 1000` (set * from any client by anyone with state power) used to blank every timeline and * get opted-in users' messages redacted within a minute. */ export const effectiveRetentionMs = (maxLifetime: unknown): number | undefined => typeof maxLifetime === 'number' && Number.isFinite(maxLifetime) && maxLifetime >= RETENTION_MIN_MS ? maxLifetime : undefined; /** The room's active retention window in ms, or `undefined` when unset/disabled. */ export const getRoomRetentionMs = (room: Room): number | undefined => { const event = room.currentState.getStateEvents(StateEvent.RoomRetention, ''); return effectiveRetentionMs(event?.getContent()?.max_lifetime); }; /** * [Gitea #298] Deletion grace: the auto-delete sweeper ignores a retention * policy for this long after it was set, so a mistaken or malicious change can * be reverted before anything is permanently redacted. (Hiding expired * messages locally is not destructive and applies at once.) */ export const RETENTION_DELETE_GRACE_MS = DAY_MS; export const retentionDeletesAllowed = (policySetAtMs: number, nowMs: number): boolean => nowMs - policySetAtMs >= RETENTION_DELETE_GRACE_MS; /** When the room's current retention policy was set (its state event's ts). */ export const getRoomRetentionSetAt = (room: Room): number | undefined => room.currentState.getStateEvents(StateEvent.RoomRetention, '')?.getTs(); /** True when an event at `tsMs` has passed the `maxLifetimeMs` retention window. */ export const isExpired = (tsMs: number, maxLifetimeMs: number, nowMs: number): boolean => maxLifetimeMs > 0 && nowMs - tsMs > maxLifetimeMs;